Added project

This commit is contained in:
2023-06-11 00:14:30 +03:00
parent a9048a9b39
commit 32c1d5fb0b
103 changed files with 9633 additions and 2 deletions

View File

@@ -1,4 +1,62 @@
# Linkchecker
Тестовое задание. Разработать REST-сервис, проверяющий работоспособность любой последовательности узлов. Каждый узел имеет уникальное имя,
вероятность, с которой откажет при обращении к нему, и счетчик успешно выполненных запросов.
## Оригинальный текст задания
Разработать REST-сервис, проверяющий работоспособность любой последовательности узлов.
Каждый узел имеет уникальное имя, вероятность, с которой откажет при обращении к нему, и счетчик успешно выполненных запросов.
Сервис должен реализовывать два POST-метода:
1. setNodes устанавливает граф из узлов, описанных выше. Формат входных данных - JSON.
Программа должна исключать циклические связи узлов.
2. checkRoute принимает набор вершин (или их идентификаторов) в формате JSON
и проходит по этим вершинам, проверяя на каждом пройденном узле, не отказал ли он.
Если путь существует в графе и ни один из узлов пути не отказал, следует увеличить счетчик
в каждом из узлов пути. В противном случае отображать ошибку в ответе POST-метода (произвольный формат).
3. Узлы и связи должны храниться в базе данных.
## Изменённый вариант задания
После введённых корректировок, итоговый вид тестового задания выглядит следующим образом:
Разработать REST-сервис, проверяющий работоспособность любой последовательности узлов.
1. Каждый узел имеет уникальное имя и счетчик успешно выполненных запросов,
так же для хранения в базе данных есть уникальный идентификатор, который
присваивается автоматически при записи в БД. Был исключен элемент вероятность отказа узла.
Этот параметр перестал быть нужным, так как вероятность отказа узла стала случайным фактором,
возникающая автоматически, во время проверки последовательности узлов.
1. Граф в программе неориентированный, т.е. вершины графа связаны друг с другом рёбрами,
не имеющими направления. В базе данных, хранение графа осуществляется в двух таблицах.
В одной таблице осуществляется хранение набора узлов графа, в другой набор рёбер графа.
Более подробно смотрите запись в wiki [Описание данных](./../wikis/Описание%20данных)
1. Программа позволяет делать следующее:
1. Работать с графом обобщённо:
1. Создавать новый граф (при этом информация о прежнем графе будет удалена с БД)
1. Извлекать информацию о графе в заданном формате
1. Удалять целиком весь граф
1. Проверять работоспособность заданной последовательности узлов
(по условию задачи) выполнив соответствующий запрос.
1. Работать с узлами и ребрами по отдельности, т.е. добавлять, удалять,
искать информацию по заданным параметрам. Ручное изменение какой-либо информации о узле и ребре не предусмотрена,
т.е. возможно либо добавления узла или ребра в БД или удаление из БД)
Так как по условию задания, граф должен исключить все виды циклов
(т.е. граф должен быть ациклическим), то при любом запросе информации о графе целиком
или при проверки набора заданных узлов будет, происходить автоматический поиск
и удаление циклов из графа. Удаление циклов происходит при помощи удаления набора рёбер,
создающие циклы, поэтому в случае обнаружения циклов в графе, набор рёбер графа будет
изменён и данные изменения попадут в БД.
Автоматический поиск и удаление циклов не срабатывает, если происходит работа только
с набором данных рёбер графа в отдельности, т.е. можно добавлять в базу рёбра,
образующие циклы.
**Используемый стек** : **Spring Boot**, **Spring Data**, **ORM (Hibernate)**,
[**JGraphT**](https://jgrapht.org/) (для работы с графом),
**GSON** (используется вместо используемого по умолчанию Jackson для работы с json),
**Thymeleaf** и **Bootstrap** (используется для формирования стартовой информационной страницы),
**Mockito** (идёт вместе с Spring Boot),
**Powermock** (подключается отдельной библиотекой, используется в дополнении к mockito для тестов)
**Хранилище данных** : PostgeSQL (для production), H2 (для тестов)

View File

@@ -0,0 +1,15 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
POST http://localhost:8080/rest/v1/graph/edges/create
Content-Type: application/json
Cache-Control: no-cache
{"nodeOne": "v6", "nodeTwo": "v7"}
###

View File

@@ -0,0 +1,15 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
POST http://localhost:8080/rest/v1/graph/edges/create/byBatch
Content-Type: application/json
Cache-Control: no-cache
[{"nodeOne": "v2", "nodeTwo": "v6"}, {"nodeOne": "v6", "nodeTwo": "v7"}, {"nodeOne": "v7", "nodeTwo": "v8"}]
###

View File

@@ -0,0 +1,13 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
DELETE http://localhost:8080/rest/v1/graph/edges
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,13 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
DELETE http://localhost:8080/rest/v1/graph/edges/byId/5005
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,13 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
DELETE http://localhost:8080/rest/v1/graph/edges/byName/v1
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,13 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
DELETE http://localhost:8080/rest/v1/graph/edges/byName?nodeOne=v3&nodeTwo=v4
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,12 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection).
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
GET http://localhost:8080/rest/v1/graph/edges/byId/5050
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,12 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection).
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
GET http://localhost:8080/rest/v1/graph/edges/byName?nodeOne=v1&nodeTwo=v2
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,12 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection).
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
GET http://localhost:8080/rest/v1/graph/edges
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,12 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection).
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
GET http://localhost:8080/rest/v1/graph/edges/byName/v1
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,15 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
POST http://localhost:8080/rest/v1/graph/nodes/create
Content-Type: application/json
Cache-Control: no-cache
{"name":"v6"}
###

View File

@@ -0,0 +1,15 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
POST http://localhost:8080/rest/v1/graph/nodes/create/byBatch
Content-Type: application/json
Cache-Control: no-cache
[{"name":"6"}, {"name":"v7"}, {"name":"v8"}]
###

View File

@@ -0,0 +1,13 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
DELETE http://localhost:8080/rest/v1/graph/nodes
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,13 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
DELETE http://localhost:8080/rest/v1/graph/nodes/byId/5010
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,13 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
DELETE http://localhost:8080/rest/v1/graph/nodes/byName/6
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,15 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
DELETE http://localhost:8080/rest/v1/graph/nodes/byObj
Content-Type: application/json
Cache-Control: no-cache
{"id": 5003, "name": "v4"}
###

View File

@@ -0,0 +1,12 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection).
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
GET http://localhost:8080/rest/v1/graph/nodes/byId/5000
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,12 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection).
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
GET http://localhost:8080/rest/v1/graph/nodes/byName/v1
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,12 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection).
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
GET http://localhost:8080/rest/v1/graph/nodes
Content-Type: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,41 @@
POST http://localhost:8080/rest/v1/graph/create
Content-Type: application/json
Cache-Control: no-cache
{
"nodes": [
{"name":"v1"},
{"name":"v2"},
{"name":"v3"},
{"name":"v4"},
{"name":"v5"},
{"name":"v6"},
{"name":"v7"},
{"name":"v8"},
{"name":"v9"},
{"name":"v10"}
],
"edges":[
{"nodeOne":"v1","nodeTwo":"v2"},
{"nodeOne":"v1","nodeTwo":"v3"},
{"nodeOne":"v1","nodeTwo":"v4"},
{"nodeOne":"v1","nodeTwo":"v5"},
{"nodeOne":"v2","nodeTwo":"v3"},
{"nodeOne":"v2","nodeTwo":"v4"},
{"nodeOne":"v2","nodeTwo":"v5"},
{"nodeOne":"v3","nodeTwo":"v4"},
{"nodeOne":"v3","nodeTwo":"v5"},
{"nodeOne":"v4","nodeTwo":"v5"},
{"nodeOne":"v6","nodeTwo":"v7"},
{"nodeOne":"v7","nodeTwo":"v8"},
{"nodeOne":"v8","nodeTwo":"v9"},
{"nodeOne":"v8","nodeTwo":"v10"},
{"nodeOne":"v9","nodeTwo":"v10"},
{"nodeOne":"v10","nodeTwo":"v7"}
]
}
<> 2019-04-05T122503.200.json
<> 2019-04-05T122347.405.html
###

View File

@@ -0,0 +1,27 @@
POST http://localhost:8080/rest/v1/graph/create
Content-Type: application/json
Cache-Control: no-cache
{
"nodes":[
{"name":"v1"},
{"name":"v2"},
{"name":"v3"},
{"name":"v4"},
{"name":"v5"}
],
"edges":[
{"nodeOne":"v1","nodeTwo":"v2"},
{"nodeOne":"v2","nodeTwo":"v3"},
{"nodeOne":"v3","nodeTwo":"v4"},
{"nodeOne":"v3","nodeTwo":"v5"},
{"nodeOne":"v5","nodeTwo":"v4"},
{"nodeOne":"v5","nodeTwo":"v2"}
]
}
<> 2019-04-04T124349.200.json
<> 2019-04-03T023130.200.json
###

View File

@@ -0,0 +1,16 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection).
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
POST http://localhost:8080/rest/v1/graph/checkroute
Content-Type: application/json
Cache-Control: no-cache
["v1", "v2", "v3"]
###

12
TestRESTful/getGraph.http Normal file
View File

@@ -0,0 +1,12 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection).
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
GET http://localhost:8080/rest/v1/graph/123
Content-Type: application/json
Cache-Control: no-cache
###

12
TestRESTful/graphViz.http Normal file
View File

@@ -0,0 +1,12 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection).
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
GET http://localhost:8080/rest/v1/graph/export
Content-Type: text/html
Cache-Control: no-cache
###

13
TestRESTful/options.http Normal file
View File

@@ -0,0 +1,13 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection) or
# paste cURL into the file and request will be converted to HTTP Request format.
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
OPTIONS http://localhost:8080/rest/v1/graph
Accept: application/json
Cache-Control: no-cache
###

View File

@@ -0,0 +1,12 @@
# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection).
#
# Following HTTP Request Live Templates are available:
# * 'gtrp' and 'gtr' create a GET request with or without query parameters;
# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body;
# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data);
DELETE http://localhost:8080/rest/v1/graph
Content-Type: application/json
Cache-Control: no-cache
###

108
linkchecker_linux.sh Executable file
View File

@@ -0,0 +1,108 @@
#!/bin/bash
EXECUTABLE_FILE=linkchecker.jar
PROPERTIES_FILE=application-production.properties
HELP="Usage: linkchecker_linux [KEY]
Script without key is run program in DEMO mode. Also vailable next switches:
--debug - running program in DEMO mode with extended debug information.
--production - running program in PRODUCTION mode. For running in this mode needed additional
file application-production.properties with PostgreSQL dataset information. Also for this mode
available addition key --debug for runnning program with extended debug information.
--help - display this is message
Examples:
linkchecker_linux - run program in DEMO mode
linkchecker_linux --debug - run program in DEMO mode with extended debug information.
linkchecker_linux --production - run program in PRODUCTION mode.
linkchecker_linux --production --debug - run program in PRODUCTION mode with extended debug information.
For more information see https://gitlab.com/Aleksandrov/linkchecker/wikis/
"
PROPERTIES_FILE_NOT_FOUND="
WARNING!
You try run program in PRODUCTION mode. For this mode need PostgreSQL but file
$PROPERTIES_FILE with dataset information is not found. Please fill next information and run program again!
"
if [ -f "$EXECUTABLE_FILE" ]; then
if [ -z "$1" ]; then
echo "Running program in DEMO mode"
java -jar linkchecker.jar
else
case "$1" in
--help)
echo "$HELP"
;;
--debug)
echo "Running program in DEMO mode with extended debug information"
java -jar linkchecker.jar --spring.profiles.active=demo,debug
;;
--production)
if [ -f "$PROPERTIES_FILE" ]; then
if [ -z "$2" ]; then
echo "Running program in PRODUCTION mode"
java -jar linkchecker.jar --spring.profiles.active=production
else
if [ "$2" = "--debug" ]; then
echo "Running program in PRODUCTION mode with extended debug information"
java -jar linkchecker.jar --spring.profiles.active=production,debug
else
echo "linkchecker: unknown option $2"
echo "Try 'linkchecker --help' for more information."
fi
fi
else
echo "$PROPERTIES_FILE_NOT_FOUND"
printf 'PostgreSQL database host name or IP address (default localhost): '
read -r LINKCHECKER_PGSQL_DB_HOST
if [ -z "$LINKCHECKER_PGSQL_DB_HOST" ]; then
LINKCHECKER_PGSQL_DB_HOST="jdbc:postgresql://localhost"
else
LINKCHECKER_PGSQL_DB_HOST="jdbc:postgresql://$LINKCHECKER_PGSQL_DB_HOST"
fi
printf 'PostgreSQL database port (default 5432): '
read -r LINKCHECKER_PGSQL_DB_PORT
if [ -z "$LINKCHECKER_PGSQL_DB_PORT" ]; then
LINKCHECKER_PGSQL_DB_PORT=5432
fi
printf 'PostgreSQL database name (default linkchecker): '
read -r LINKCHECKER_PGSQL_DB_NAME
if [ -z "$LINKCHECKER_PGSQL_DB_NAME" ]; then
LINKCHECKER_PGSQL_DB_NAME="linkchecker"
fi
printf 'PostgreSQL database user name: '
read -r LINKCHECKER_PGSQL_DB_USER
printf 'PostgreSQL database password: '
read -r -s LINKCHECKER_PGSQL_DB_PASSWORD
echo
touch "$PROPERTIES_FILE"
{
echo "LINKCHECKER_PGSQL_DB_HOST=$LINKCHECKER_PGSQL_DB_HOST"
echo "LINKCHECKER_PGSQL_DB_PORT=$LINKCHECKER_PGSQL_DB_PORT"
echo "LINKCHECKER_PGSQL_DB_NAME=$LINKCHECKER_PGSQL_DB_NAME"
echo "LINKCHECKER_PGSQL_DB_USER=$LINKCHECKER_PGSQL_DB_USER"
echo "LINKCHECKER_PGSQL_DB_PASSWORD=$LINKCHECKER_PGSQL_DB_PASSWORD"
} > "$PROPERTIES_FILE"
fi
;;
*)
echo "linkchecker_linux: unknown option $1"
echo "Try 'linkchecker_linux --help' for more information."
;;
esac
fi
else
echo "Executable file linkchecker.jar is not found!"
fi

136
linkchecker_win.bat Normal file
View File

@@ -0,0 +1,136 @@
@echo off
set EXECUTABLE_FILE=linkchecker.jar
set PROPERTIES_FILE=application-production.properties
if not exist %EXECUTABLE_FILE% (
echo Executable file linkchecker.jar is not found!
exit /b
)
if ""=="%1" (
echo Running program in DEMO mode
java -jar %EXECUTABLE_FILE%
) else (
if "--help"=="%1" goto :Help
if "--debug"=="%1" (
echo Running program in DEMO mode with extended debug information
java -jar linkchecker.jar --spring.profiles.active=demo,debug
) else (
if "--production"=="%1" (
if exist %PROPERTIES_FILE% (
if ""=="%2" (
echo Running program in PRODUCTION mode
java -jar linkchecker.jar --spring.profiles.active=production
) else (
if "--debug"=="%2" (
echo Running program in PRODUCTION mode with extended debug information
java -jar linkchecker.jar --spring.profiles.active=production,debug
) else goto :KEY_NOT_FOUND %2
)
) else (
goto :PROPERTIES_FILE_NOT_FOUND
)
) else goto :KEY_NOT_FOUND %1
)
)
exit /B
:Help
echo Usage: linkchecker_win [KEY]
echo.
echo Script without key is run program in DEMO mode. Also vailable next switches:
echo.
echo --debug - running program in DEMO mode with extended debug information.
echo.
echo --production - running program in PRODUCTION mode. For running in this mode needed additional file application-production.properties with PostgreSQL dataset information. Also for this mode available addition key --debug for runnning program with extended debug information.
echo.
echo --help - display this is message
echo.
echo Examples:
echo.
echo linkchecker_win - run program in DEMO mode
echo.
echo linkchecker_win --debug - run program in DEMO mode with extended debug information.
echo.
echo linkchecker_win --production - run program in PRODUCTION mode.
echo.
echo linkchecker_win --production --debug - run program in PRODUCTION mode with extended debug information.
echo.
echo For more information see https://gitlab.com/Aleksandrov/linkchecker/wikis/
exit /b
:KEY_NOT_FOUND
echo linkchecker_win: unknown option %~1
echo Try 'linkchecker_win --help' for more information.
exit /b
:PROPERTIES_FILE_NOT_FOUND
setlocal
echo WARNING!
echo.
echo You try run program in PRODUCTION mode. For this mode need PostgreSQL but file %PROPERTIES_FILE% with dataset information is not found. Please fill next information and run program again!
echo.
set /p PRMT="PostgreSQL database host name or IP address (default localhost): "
if ""=="%PRMT%" (
set LINKCHECKER_PGSQL_DB_HOST=jdbc:postgresql://localhost
) else (
set LINKCHECKER_PGSQL_DB_HOST=jdbc:postgresql://%PRMT%
)
set /p PRMT1="PostgreSQL database port (default 5432): "
if ""=="%PRMT1%" set LINKCHECKER_PGSQL_DB_PORT=5432
set /p PRMT2="PostgreSQL database name (default linkchecker): "
if ""=="%PRMT2%" set LINKCHECKER_PGSQL_DB_NAME=linkchecker
set /p LINKCHECKER_PGSQL_DB_USER="PostgreSQL database user name: "
call :getPassword LINKCHECKER_PGSQL_DB_PASSWORD "PostgreSQL database password: "
echo.
echo LINKCHECKER_PGSQL_DB_HOST=%LINKCHECKER_PGSQL_DB_HOST%>%PROPERTIES_FILE%
echo LINKCHECKER_PGSQL_DB_PORT=%LINKCHECKER_PGSQL_DB_PORT%>>%PROPERTIES_FILE%
echo LINKCHECKER_PGSQL_DB_NAME=%LINKCHECKER_PGSQL_DB_NAME%>>%PROPERTIES_FILE%
echo LINKCHECKER_PGSQL_DB_USER=%LINKCHECKER_PGSQL_DB_USER%>>%PROPERTIES_FILE%
echo LINKCHECKER_PGSQL_DB_PASSWORD=%LINKCHECKER_PGSQL_DB_PASSWORD%>>%PROPERTIES_FILE%
endlocal
exit /b
::------------------------------------------------------------------------------
:: Masks user input and returns the input as a variable.
:: Password-masking code based on http://www.dostips.com/forum/viewtopic.php?p=33538#p33538
::
:: Arguments: %1 - the variable to store the password in
:: %2 - the prompt to display when receiving input
::------------------------------------------------------------------------------
:getPassword
set "_password="
:: We need a backspace to handle character removal
for /f %%a in ('"prompt;$H&for %%b in (0) do rem"') do set "BS=%%a"
:: Prompt the user
set /p "=%~2" <nul
:keyLoop
:: Retrieve a keypress
set "key="
for /f "delims=" %%a in ('xcopy /l /w "%~f0" "%~f0" 2^>nul') do if not defined key set "key=%%a"
set "key=%key:~-1%"
:: If No keypress (enter), then exit
:: If backspace, remove character from password and console
:: Otherwise, add a character to password and go ask for next one
if defined key (
if "%key%"=="%BS%" (
if defined _password (
set "_password=%_password:~0,-1%"
set /p "=!BS! !BS!"<nul
)
) else (
set "_password=%_password%%key%"
set /p "="<nul
)
goto :keyLoop
)
echo/
:: Return password to caller
set "%~1=%_password%"
goto :eof

286
mvnw vendored Executable file
View File

@@ -0,0 +1,286 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
# TODO classpath?
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
wget "$jarUrl" -O "$wrapperJarPath"
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
curl -o "$wrapperJarPath" "$jarUrl"
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

161
mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,161 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
echo Found %WRAPPER_JAR%
) else (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
echo Finished downloading %WRAPPER_JAR%
)
@REM End of extension
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

180
pom.xml Normal file
View File

@@ -0,0 +1,180 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ru.resprojects</groupId>
<artifactId>linkchecker</artifactId>
<version>0.3.0</version>
<name>linkchecker</name>
<description>Linkchecker (test project)</description>
<properties>
<java.version>1.8</java.version>
<jgrapht.version>1.3.1</jgrapht.version>
<bootstrap.version>4.3.1</bootstrap.version>
<gson.version>2.8.6</gson.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- Exclude the default Jackson dependency -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- JSON -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!-- DATABASES -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- BOOTSTRAP -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>${bootstrap.version}</version>
</dependency>
<!--JGraphT-->
<dependency>
<groupId>org.jgrapht</groupId>
<artifactId>jgrapht-core</artifactId>
<version>${jgrapht.version}</version>
</dependency>
<dependency>
<groupId>org.jgrapht</groupId>
<artifactId>jgrapht-ext</artifactId>
<version>${jgrapht.version}</version>
</dependency>
<dependency>
<groupId>org.jgrapht</groupId>
<artifactId>jgrapht-io</artifactId>
<version>${jgrapht.version}</version>
</dependency>
<!-- Unit TEST -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Documentation.java</include>
<include>**/*Tests.java</include>
</includes>
</configuration>
</plugin>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.8</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
<doctype>book</doctype>
<attributes>
<snippets>${project.build.directory}/generated-snippets</snippets>
</attributes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.outputDirectory}/static/docs
</outputDirectory>
<resources>
<resource>
<directory>${project.build.directory}/generated-docs</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,152 @@
= Linkchecker API Guide v1
Test project;
:doctype: book
:toc:
:sectanchors:
:sectlinks:
:toclevels: 5
:source-highlighter: highlightjs
[[overview]]
= Обзор
*Linkchecker* - REST-сервис проверяющий работоспособность любой последовательности узлов, хранящихся в неориентированном ациклическом графе.
API REST-сервиса разбит на следующие категории:
* *GRAPH API* - набор команд позволяющий работать с графом в целом.
* *NODES API* - набор команд позволящий работать отдельно с вершинами графа.
* *EDGES API* - набор команд позволящий работать отдельно с рёбрами графа.
[[overview-common]]
== Общая информация
[[overview-http-verbs]]
=== HTTP методы
API Linkchecker следует стандартным соглашениям HTTP REST и поддерживает HTTP методы, перечисленные ниже.
|===
| Вид запроса | Назначание
| `GET`
| Используется для запроса содержимого указанного ресурса.
| `POST`
| Применяется для передачи пользовательских данных заданному ресурсу (создание нового ресурса или передача данных для обработки на стороне сервера).
| `DELETE`
| Удаляет указанный ресурс.
| `OPTIONS`
| Предоставляет информацию клиенту о доступных для работы HTTP методов.
|===
[[overview-http-status-codes]]
=== Коды состояния HTTP
В API Linkchecker используются следующие коды состояния HTTP.
|===
| Код состояния | Назначение
| `200 OK`
| успешный запрос. Если клиентом были запрошены какие-либо данные, то они находятся в заголовке и/или теле сообщения.
| `201 Created`
| В результате успешного выполнения запроса был создан новый ресурс.
| `204 No Content`
| сервер успешно обработал запрос, но в ответе были переданы только заголовки без тела сообщения.
| `422 Unprocessable Entity`
| сервер успешно принял запрос, может работать с указанным видом данных (например, в теле запроса находится XML-документ, имеющий верный синтаксис), однако имеется какая-то логическая ошибка, из-за которой невозможно произвести операцию над ресурсом
| `500 Internal Server Error`
| любая внутренняя ошибка сервера, которая не входит в рамки остальных ошибок класса.
|===
[[overview-http-errors]]
=== Ошибки
В случае возникновения ошибок во время работы REST-сервиса, клиенту будет возвращен структурированный набор данных в формате JSON
[source,json]
----
{
"url": "request_link",
"type": "APP_ERROR[,DATA_NOT_FOUND,DATA_ERROR,VALIDATION_ERROR, WRONG_REQUEST]",
"place": "APP[,GRAPH,NODE,EDGE]",
"messages": [
"Any message 1",
"Any message 2"
]
}
----
, где
* *url* - REST-запрос, при котором возникла ошибка
* *type* - тип ошибки
** _APP_ERROR_ - общие ошибки сервиса.
** _DATA_NOT_FOUND_ - ошибки, возникающие при поиске и извлечении данных.
** _DATA_ERROR_ - ошибки, возникающие при обработки данных.
** _VALIDATION_ERROR_ - ошибки, связанные с проверкой данных.
** _WRONG_REQUEST_ - ошибки, возникающие при некорректном запросе.
* *place* - места возникновения ошибок
** _APP_ - сервис
** _GRAPH_ - граф
** _NODE_ - вершины графа
** _EDGE_ - рёбра графа
* *messages* - сообщения об ошибках
Виды сообщения об ошибках:
*Обобщенные сообщения об ошибках*
* Argument must not be null - на вход вместо объекта был подан null
* Collection must not be empty - на вход поступила пустая коллекция данных
* Collection must not contain a null item - на вход поступила коллекция, в которой содержится null элемент
* Collection must have more than one element - в определённых случаях требуется что бы коллекция состояла как минимум из двух элементов (например при работе с рёбрами графа, где требуется указать две вершины графа, которые нужно связать)
* %s with ID = %d is not found - данные с указанным ID не обнаружены, где %s - может быть или NODE или EDGE, %d - номер id'шника.
*Сообщения об ошибках, возникающие при работе с рёбрами графа*
* Edge for nodes [%s, %s] is not found - ребро для указанных вершин (нод) не найдено, [%s, %s] - уникальные имена пары вершин.
* Edge for nodes ([%s, %s], [%s, %s]) already present in the graph - данное сообщение возникает при попытки добавить в граф уже существующее ребро. Где ([%s, %s], [%s, %s]) - подставляются уникальные имена вершин графа. Так как граф неориентированный, то например v1 и v2 <=> v2 и v1.
* Edges for node %s is not found - данное сообщение возникает при попытки извлечь информацию по рёбрам графа для заданной вершины, %s - уникальное имя вершины графа.
*Сообщения об ошибках, возникающие при работе с вершинами (нодами) графа*
* Node %s already present in the graph - данное сообщение возникает, при попытки добавить вершину, которая уже присутствует в графе.
* Error while update node with id = - сообщение возникает при неудачной попытки обновить данные по вершине графа, где id - номер идентификатора вершины.
* Node with NAME = %s is not found - сообщение возникает, при неудачном поиске вершины графа по её уникальному имени.
* Node %s is not found - сообщение возникает, при неудачном поиске вершины графа в режиме поиска по заданному объекту.
* Nodes %s and %s are not reachable to each other - сообщение возникает если один из узлов (вершин графа) не достижим до другого узла (т.е. имеются промежуточные узлы).
* Node %s is fault - сообщение возникает в случае генерации сбоя в узле (в вершине графа), т.е. узел оказался недоступным во время обхода по узлам.
[[rest-api-usage]]
= REST API
Далее - описание отдельных частей API REST-сервиса.
[[rest-api-usage-graph]]
== GRAPH API
Набор запросов при работе с графом в целом.
include::graph.adoc[]
[[rest-api-usage-nodes]]
== NODES API
Набор запросов при работе с вершинами графа (нодами).
include::nodes.adoc[]
[[rest-api-usage-edges]]
== EDGES API
Набор запросов при работе с рёбрами графа.
include::edges.adoc[]

View File

@@ -0,0 +1,468 @@
=== HTTP запросы для поиска рёбер графа
==== Получить список всех рёбер графа.
*Пример запроса*
'''
*CURL request*
include::{snippets}/get-edges/curl-request.adoc[]
*HTTP request*
include::{snippets}/get-edges/http-request.adoc[]
*HTTPie request*
include::{snippets}/get-edges/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/get-edges/http-response.adoc[]
*Response fields*
include::{snippets}/get-edges/response-fields.adoc[]
==== HTTP запросы для поиска рёбер графа.
===== Поиск ребра графа по его идентификатору.
*Пример запроса*
'''
*CURL request*
include::{snippets}/get-edge-by-id/curl-request.adoc[]
*HTTP request*
include::{snippets}/get-edge-by-id/http-request.adoc[]
*HTTPie request*
include::{snippets}/get-edge-by-id/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/get-edge-by-id/http-response.adoc[]
*Response fields*
include::{snippets}/get-edge-by-id/response-fields.adoc[]
===== Поиск рёбер графа в которых встречается уникальное имя вершины графа заданное в качестве параметра поиска
В поиске используется уникальное имя вершины графа. В отбор попадают все ребра графа в которых встречается
уникальное имя вершины графа, заданное в качестве параметра поиска.
*Пример запроса*
'''
*CURL request*
include::{snippets}/get-edges-by-node-name/curl-request.adoc[]
*HTTP request*
include::{snippets}/get-edges-by-node-name/http-request.adoc[]
*HTTPie request*
include::{snippets}/get-edges-by-node-name/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/get-edges-by-node-name/http-response.adoc[]
*Response fields*
include::{snippets}/get-edges-by-node-name/response-fields.adoc[]
===== Поиск ребра графа по паре уникальных имен вершин графа, которые связывает искомое ребро
Поиск ребра ведётся по конкретному набору вершин, которые связывает искомое ребро.
*Пример запроса*
'''
*CURL request*
include::{snippets}/get-edge-by-nodes-name/curl-request.adoc[]
*HTTP request*
include::{snippets}/get-edge-by-nodes-name/http-request.adoc[]
*HTTPie request*
include::{snippets}/get-edge-by-nodes-name/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/get-edge-by-nodes-name/http-response.adoc[]
*Response fields*
include::{snippets}/get-edge-by-nodes-name/response-fields.adoc[]
==== Примеры ошибок при поиске рёбер графа
===== Ошибка поиска ребра графа по идентификатору
*Пример запроса*
'''
*CURL request*
include::{snippets}/get-edge-exception-1/curl-request.adoc[]
*HTTP request*
include::{snippets}/get-edge-exception-1/http-request.adoc[]
*HTTPie request*
include::{snippets}/get-edge-exception-1/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/get-edge-exception-1/http-response.adoc[]
*Response fields*
include::{snippets}/get-edge-exception-1/response-fields.adoc[]
===== Ошибка поиска рёбер графа по уникальному имени вершины графа
*Пример запроса*
'''
*CURL request*
include::{snippets}/get-edge-exception-2/curl-request.adoc[]
*HTTP request*
include::{snippets}/get-edge-exception-2/http-request.adoc[]
*HTTPie request*
include::{snippets}/get-edge-exception-2/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/get-edge-exception-2/http-response.adoc[]
*Response fields*
include::{snippets}/get-edge-exception-2/response-fields.adoc[]
=== HTTP запросы добавления новых рёбер в граф
==== Создать и добавить новое ребро в граф
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-edge/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-edge/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-edge/httpie-request.adoc[]
*Request body*
include::{snippets}/create-edge/request-body.adoc[]
*Request fields*
include::{snippets}/create-edge/request-fields.adoc[]
*Пример ответа*
*HTTP response*
include::{snippets}/create-edge/http-response.adoc[]
*Response body*
include::{snippets}/create-edge/response-body.adoc[]
*Response fields*
include::{snippets}/create-edge/response-fields.adoc[]
==== Создать и добавить новые ребра в граф
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-edges/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-edges/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-edges/httpie-request.adoc[]
*Request body*
include::{snippets}/create-edges/request-body.adoc[]
*Request fields*
include::{snippets}/create-edges/request-fields.adoc[]
*Пример ответа*
*HTTP response*
include::{snippets}/create-edges/http-response.adoc[]
*Response body*
include::{snippets}/create-edges/response-body.adoc[]
*Response fields*
include::{snippets}/create-edges/response-fields.adoc[]
==== Примеры ошибок возникающие при добавлении рёбер в граф
===== Ошибка валидации при добавлении ребра в граф
Попытка создать новый объект ребра графа не указав имена связываемых ребром вершин графа.
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-edge-exception-1/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-edge-exception-1/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-edge-exception-1/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/create-edge-exception-1/http-response.adoc[]
*Response fields*
include::{snippets}/create-edge-exception-1/response-fields.adoc[]
===== Ошибка при создания ребра, который уже присутствует в графе
Попытка создать новый объект ребра, который уже присутствует в графе.
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-edge-exception-2/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-edge-exception-2/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-edge-exception-2/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/create-edge-exception-2/http-response.adoc[]
*Response fields*
include::{snippets}/create-edge-exception-2/response-fields.adoc[]
===== Ошибка создания набора рёбер графа используя пустой список
Попытка создать и добавить в граф набор рёбер графа используя пустой список.
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-edge-exception-3/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-edge-exception-3/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-edge-exception-3/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/create-edge-exception-3/http-response.adoc[]
*Response fields*
include::{snippets}/create-edge-exception-3/response-fields.adoc[]
===== Ошибка создания набора вершин рёбер используя список содержащий null-элемент
Попытка создать и добавить в граф набор рёбер графа используя список содержащий null-элемент.
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-edge-exception-4/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-edge-exception-4/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-edge-exception-4/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/create-edge-exception-4/http-response.adoc[]
*Response fields*
include::{snippets}/create-edge-exception-4/response-fields.adoc[]
===== Ошибка создания набора рёбер графа используя список содержащий существующее ребро в графе
Попытка создать и добавить в граф набор рёбер графа используя список содержащий существующее ребро в графе.
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-edge-exception-5/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-edge-exception-5/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-edge-exception-5/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/create-edge-exception-5/http-response.adoc[]
*Response fields*
include::{snippets}/create-edge-exception-5/response-fields.adoc[]
=== HTTP запросы для удаления рёбер графа
==== Удалить все рёбра графа
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-all-edges/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-all-edges/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-all-edges/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-all-edges/http-response.adoc[]
==== HTTP запросы для удаления ребра графа
===== Поиск и удаление ребра графа по идентификатору
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-edge-by-id/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-edge-by-id/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-edge-by-id/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-edge-by-id/http-response.adoc[]
===== Поиск и удаление всех рёбер графа которые содержат указанное в качестве параметра поиска имя вершины графа
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-edges-by-node-name/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-edges-by-node-name/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-edges-by-node-name/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-edges-by-node-name/http-response.adoc[]
===== Поиск и удаление ребра графа по именам вершин графа, которые искомое ребро соединяет
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-edge-by-nodes-name/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-edge-by-nodes-name/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-edge-by-nodes-name/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-edge-by-nodes-name/http-response.adoc[]
==== Примеры ошибок возникающие при удалении рёбер графа
===== Ошибка при поиске и удалении ребра графа по идентификатору
Попытка найти и удалить ребро графа используя идентификатор
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-edge-exception-1/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-edge-exception-1/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-edge-exception-1/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-edge-exception-1/http-response.adoc[]
*Response fields*
include::{snippets}/delete-edge-exception-1/response-fields.adoc[]
===== Ошибка при поиске и удалении рёбр графа которые не содержат указанное в качестве параметра поиска имя вершины графа
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-edge-exception-2/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-edge-exception-2/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-edge-exception-2/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-edge-exception-2/http-response.adoc[]
*Response fields*
include::{snippets}/delete-edge-exception-2/response-fields.adoc[]
===== Ошибка при поиске и удалении ребра графа по именам вершин графа
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-edge-exception-3/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-edge-exception-3/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-edge-exception-3/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-edge-exception-3/http-response.adoc[]
*Response fields*
include::{snippets}/delete-edge-exception-3/response-fields.adoc[]

View File

@@ -0,0 +1,215 @@
=== Получить объект графа.
*Пример запроса*
'''
*CURL request*
include::{snippets}/get-graph/curl-request.adoc[]
*HTTP request*
include::{snippets}/get-graph/http-request.adoc[]
*HTTPie request*
include::{snippets}/get-graph/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/get-graph/http-response.adoc[]
*HTTP response fields*
include::{snippets}/get-graph/response-fields.adoc[]
=== Экспорт графа в формат https://www.graphviz.org/about/[graphviz].
*Пример запроса*
'''
*CURL request*
include::{snippets}/export-graph/curl-request.adoc[]
*HTTP request*
include::{snippets}/export-graph/http-request.adoc[]
*HTTPie request*
include::{snippets}/export-graph/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/export-graph/http-response.adoc[]
=== Создание нового графа
При этом прежний граф полностью удаляется из БД.
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-graph/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-graph/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-graph/httpie-request.adoc[]
*Request body*
include::{snippets}/create-graph/request-body.adoc[]
*Request fields*
include::{snippets}/create-graph/request-fields.adoc[]
*Пример ответа*
*HTTP response*
include::{snippets}/create-graph/http-response.adoc[]
*Response body*
include::{snippets}/create-graph/response-body.adoc[]
*Response fields*
include::{snippets}/create-graph/response-fields.adoc[]
==== Примеры ошибок
===== Ошибка при создании нового графа
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-graph-exception/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-graph-exception/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-graph-exception/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/create-graph-exception/http-response.adoc[]
*Response body*
include::{snippets}/create-graph-exception/response-body.adoc[]
*Response fields*
include::{snippets}/create-graph-exception/response-fields.adoc[]
=== Удалить графа из БД
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-graph/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-graph/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-graph/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-graph/http-response.adoc[]
=== Проверка работоспособности заданной последовательности узлов.
*Пример запроса*
'''
*CURL request*
include::{snippets}/checkroute-graph/curl-request.adoc[]
*HTTP request*
include::{snippets}/checkroute-graph/http-request.adoc[]
*HTTPie request*
include::{snippets}/checkroute-graph/httpie-request.adoc[]
*Request body*
include::{snippets}/checkroute-graph/request-body.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/checkroute-graph/http-response.adoc[]
*Response body*
include::{snippets}/checkroute-graph/response-body.adoc[]
==== Примеры ошибок
===== Пустая входная коллекция
*Пример запроса*
'''
*CURL request*
include::{snippets}/checkroute-graph-exception-1/curl-request.adoc[]
*HTTP request*
include::{snippets}/checkroute-graph-exception-1/http-request.adoc[]
*HTTPie request*
include::{snippets}/checkroute-graph-exception-1/httpie-request.adoc[]
*Request body*
include::{snippets}/checkroute-graph-exception-1/request-body.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/checkroute-graph-exception-1/http-response.adoc[]
*Response fields*
include::{snippets}/checkroute-graph-exception-1/response-fields.adoc[]
===== Входная коллекция состоящая из одного элемента
*Пример запроса*
'''
*CURL request*
include::{snippets}/checkroute-graph-exception-2/curl-request.adoc[]
*HTTP request*
include::{snippets}/checkroute-graph-exception-2/http-request.adoc[]
*HTTPie request*
include::{snippets}/checkroute-graph-exception-2/httpie-request.adoc[]
*Request body*
include::{snippets}/checkroute-graph-exception-2/request-body.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/checkroute-graph-exception-2/http-response.adoc[]
*Response fields*
include::{snippets}/checkroute-graph-exception-2/response-fields.adoc[]
===== Путь между входными узлами не найден
*Пример запроса*
*CURL request*
include::{snippets}/checkroute-graph-exception-3/curl-request.adoc[]
*HTTP request*
include::{snippets}/checkroute-graph-exception-3/http-request.adoc[]
*HTTPie request*
include::{snippets}/checkroute-graph-exception-3/httpie-request.adoc[]
*Request body*
include::{snippets}/checkroute-graph-exception-3/request-body.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/checkroute-graph-exception-3/http-response.adoc[]
*Response fields*
include::{snippets}/checkroute-graph-exception-3/response-fields.adoc[]

View File

@@ -0,0 +1,473 @@
=== HTTP запросы для поиска вершин графа
==== Получить список всех вершин графа.
*Пример запроса*
'''
*CURL request*
include::{snippets}/get-nodes/curl-request.adoc[]
*HTTP request*
include::{snippets}/get-nodes/http-request.adoc[]
*HTTPie request*
include::{snippets}/get-nodes/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/get-nodes/http-response.adoc[]
*Response fields*
include::{snippets}/get-nodes/response-fields.adoc[]
==== HTTP запросы поиска вершины графа.
===== Найти вершину графа по её идентификатору.
*Пример запроса*
'''
*CURL request*
include::{snippets}/get-node-by-id/curl-request.adoc[]
*HTTP request*
include::{snippets}/get-node-by-id/http-request.adoc[]
*HTTPie request*
include::{snippets}/get-node-by-id/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/get-node-by-id/http-response.adoc[]
*Response fields*
include::{snippets}/get-node-by-id/response-fields.adoc[]
===== Найти вершину графа по её уникальному имени.
*Пример запроса*
'''
*CURL request*
include::{snippets}/get-node-by-name/curl-request.adoc[]
*HTTP request*
include::{snippets}/get-node-by-name/http-request.adoc[]
*HTTPie request*
include::{snippets}/get-node-by-name/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/get-node-by-name/http-response.adoc[]
*Response fields*
include::{snippets}/get-node-by-name/response-fields.adoc[]
==== Примеры ошибок при поиске вершин(ы) графа
===== Ошибка поиска вершины графа по уникальному имени
*Пример запроса*
'''
*CURL request*
include::{snippets}/get-node-by-name-exception/curl-request.adoc[]
*HTTP request*
include::{snippets}/get-node-by-name-exception/http-request.adoc[]
*HTTPie request*
include::{snippets}/get-node-by-name-exception/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/get-node-by-name-exception/http-response.adoc[]
*Response fields*
include::{snippets}/get-node-by-name-exception/response-fields.adoc[]
=== HTTP запросы добавления новых вершин в граф
==== Создать и добавить новую вершину в граф
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-node/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-node/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-node/httpie-request.adoc[]
*Request body*
include::{snippets}/create-node/request-body.adoc[]
*Request fields*
include::{snippets}/create-node/request-fields.adoc[]
*Пример ответа*
*HTTP response*
include::{snippets}/create-node/http-response.adoc[]
*Response body*
include::{snippets}/create-node/response-body.adoc[]
*Response fields*
include::{snippets}/create-node/response-fields.adoc[]
==== Создать и добавить новые вершины в граф
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-nodes/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-nodes/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-nodes/httpie-request.adoc[]
*Request body*
include::{snippets}/create-nodes/request-body.adoc[]
*Request fields*
include::{snippets}/create-nodes/request-fields.adoc[]
*Пример ответа*
*HTTP response*
include::{snippets}/create-nodes/http-response.adoc[]
*Response body*
include::{snippets}/create-nodes/response-body.adoc[]
*Response fields*
include::{snippets}/create-nodes/response-fields.adoc[]
==== Примеры ошибок возникающие при добавлении вершин в граф
===== Ошибка валидации при добавлении вершины в граф
Попытка создать новый объект вершины графа с пустым уникальным именем.
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-node-exception-1/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-node-exception-1/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-node-exception-1/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/create-node-exception-1/http-response.adoc[]
*Response fields*
include::{snippets}/create-node-exception-1/response-fields.adoc[]
===== Ошибка создания уже существующей вершины
Попытка создать новый объект вершины графа с уникальным именем, который уже существует в графе.
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-node-exception-2/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-node-exception-2/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-node-exception-2/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/create-node-exception-2/http-response.adoc[]
*Response fields*
include::{snippets}/create-node-exception-2/response-fields.adoc[]
===== Ошибка создания набора вершин графа используя пустой список
Попытка создать и добавить в граф набор вершин графа используя пустой список.
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-node-exception-3/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-node-exception-3/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-node-exception-3/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/create-node-exception-3/http-response.adoc[]
*Response fields*
include::{snippets}/create-node-exception-3/response-fields.adoc[]
===== Ошибка создания набора вершин графа используя список содержащий null-элемент
Попытка создать и добавить в граф набор вершин графа используя список содержащий null-элемент.
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-node-exception-4/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-node-exception-4/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-node-exception-4/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/create-node-exception-4/http-response.adoc[]
*Response fields*
include::{snippets}/create-node-exception-4/response-fields.adoc[]
===== Ошибка создания набора вершин графа используя список содержащий существующую вершину в графе
Попытка создать и добавить в граф набор вершин графа используя список содержащий существующую вершину в графе.
*Пример запроса*
'''
*CURL request*
include::{snippets}/create-node-exception-5/curl-request.adoc[]
*HTTP request*
include::{snippets}/create-node-exception-5/http-request.adoc[]
*HTTPie request*
include::{snippets}/create-node-exception-5/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/create-node-exception-5/http-response.adoc[]
*Response fields*
include::{snippets}/create-node-exception-5/response-fields.adoc[]
=== HTTP запросы для удаления вершин графа
==== Удалить все вершины графа
При удалении вершин графа происходит автоматическое удаление рёбер графа. Данный запрос эквивалентен запросу удаления всего графа.
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-all-nodes/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-all-nodes/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-all-nodes/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-all-nodes/http-response.adoc[]
==== HTTP запросы для удаления вершины графа
===== Поиск и удаление вершины графа по идентификатору
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-node-by-id/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-node-by-id/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-node-by-id/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-node-by-id/http-response.adoc[]
===== Поиск и удаление вершины графа по имени
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-node-by-name/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-node-by-name/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-node-by-name/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-node-by-name/http-response.adoc[]
===== Поиск и удаление вершины графа по объекту
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-node-by-obj/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-node-by-obj/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-node-by-obj/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-node-by-obj/http-response.adoc[]
==== Примеры ошибок возникающие при удалении вершин графа
===== Ошибка при поиске и удалении вершины графа по идентификатору
Попытка найти и удалить вершину графа используя идентификатор
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-node-exception-1/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-node-exception-1/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-node-exception-1/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-node-exception-1/http-response.adoc[]
*Response fields*
include::{snippets}/delete-node-exception-1/response-fields.adoc[]
===== Ошибка при поиске и удалении вершины графа по имени
Попытка найти и удалить вершину графа используя уникальное имя
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-node-exception-2/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-node-exception-2/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-node-exception-2/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-node-exception-2/http-response.adoc[]
*Response fields*
include::{snippets}/delete-node-exception-2/response-fields.adoc[]
===== Ошибка при поиске и удалении вершины графа по объекту вершины графа у которого id = null
Попытка найти и удалить вершину графа используя объект вершины графа у которого id = null
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-node-exception-3/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-node-exception-3/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-node-exception-3/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-node-exception-3/http-response.adoc[]
*Response fields*
include::{snippets}/delete-node-exception-3/response-fields.adoc[]
===== Ошибка при поиске и удалении вершины графа по объекту несуществующей вершины графа
Попытка найти и удалить вершину графа используя объект несуществующей вершины графа
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-node-exception-4/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-node-exception-4/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-node-exception-4/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-node-exception-4/http-response.adoc[]
*Response fields*
include::{snippets}/delete-node-exception-4/response-fields.adoc[]
===== Ошибка при поиске и удалении вершины графа по объекту вершины графа у которого указан некорректный id
Попытка найти и удалить вершину графа используя объект вершины графа у которого указан некорректный id
*Пример запроса*
'''
*CURL request*
include::{snippets}/delete-node-exception-5/curl-request.adoc[]
*HTTP request*
include::{snippets}/delete-node-exception-5/http-request.adoc[]
*HTTPie request*
include::{snippets}/delete-node-exception-5/httpie-request.adoc[]
*Пример ответа*
'''
*HTTP response*
include::{snippets}/delete-node-exception-5/http-response.adoc[]
*Response fields*
include::{snippets}/delete-node-exception-5/response-fields.adoc[]

View File

@@ -0,0 +1,52 @@
package ru.resprojects.linkchecker;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* Data class for application error string messages. These messages is loaded
* from "appmsg" block in the application.yml file
*
* Messages is divided on three type:
*
* AppMsg - error messages is related with application as whole.
* NodeMsg - error messages is related with nodes in the graph.
* EdgeMsg - error messages is related with edges int the graph.
*/
@Component
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "appmsg")
public class AppProperties {
private Map<String, String> appMsg = new HashMap<>();
private Map<String, String> nodeMsg = new HashMap<>();
private Map<String, String> edgeMsg = new HashMap<>();
public Map<String, String> getAppMsg() {
return appMsg;
}
public Map<String, String> getNodeMsg() {
return nodeMsg;
}
public Map<String, String> getEdgeMsg() {
return edgeMsg;
}
public void setAppMsg(Map<String, String> appMsg) {
this.appMsg = appMsg;
}
public void setNodeMsg(Map<String, String> nodeMsg) {
this.nodeMsg = nodeMsg;
}
public void setEdgeMsg(Map<String, String> edgeMsg) {
this.edgeMsg = edgeMsg;
}
}

View File

@@ -0,0 +1,20 @@
package ru.resprojects.linkchecker;
/**
* The interface indicates that the object has an identifier.
*/
public interface HasId {
/**
* Get entity id.
* @return entity id.
*/
Integer getId();
/**
* Set entity id.
* @param id of entity.
*/
void setId(Integer id);
}

View File

@@ -0,0 +1,13 @@
package ru.resprojects.linkchecker;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LinkcheckerApplication {
public static void main(String[] args) {
SpringApplication.run(LinkcheckerApplication.class, args);
}
}

View File

@@ -0,0 +1,39 @@
package ru.resprojects.linkchecker.dto;
import ru.resprojects.linkchecker.HasId;
/**
* Abstract base class for data transfer object.
*/
abstract public class BaseDto implements HasId {
/**
* ID of transport object.
*/
protected Integer id;
/**
* Default ctor.
*/
BaseDto() {
}
/**
* Ctor.
* @param id of transport object.
*/
BaseDto(final Integer id) {
this.id = id;
}
@Override
public Integer getId() {
return id;
}
@Override
public void setId(final Integer id) {
this.id = id;
}
}

View File

@@ -0,0 +1,281 @@
package ru.resprojects.linkchecker.dto;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import static ru.resprojects.linkchecker.util.ValidationUtil.*;
/**
* Class for transfer object that implements graph.
*/
public class GraphDto {
/**
* Class for transfer object that implements graph node.
*/
public static class NodeGraph extends BaseDto {
/**
* Unique graph node name.
*/
@NotBlank(message = VALIDATOR_NODE_NOT_BLANK_NAME_MESSAGE)
@Size(min = MIN_NAME_SIZE, max = MAX_NAME_SIZE,
message = VALIDATOR_NODE_NAME_RANGE_MESSAGE)
private String name;
/**
* The number of passes through the graph node.
*/
private int counter;
/**
* Default ctor.
*/
public NodeGraph() {
}
/**
* Ctor.
* @param name - unique graph node name.
*/
public NodeGraph(final String name) {
this(null, name, NODE_COUNTER_DEFAULT);
}
/**
* Ctor.
* @param id - identity number of graph node.
* @param name - unique graph node name.
* @param counter - the number of passes through the current node.
*/
public NodeGraph(final Integer id, final String name, final int counter) {
super(id);
this.name = name;
this.counter = counter;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public int getCounter() {
return counter;
}
public void setCounter(final int counter) {
this.counter = counter;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NodeGraph nodeGraph = (NodeGraph) o;
return Objects.equals(id, nodeGraph.id)
&& counter == nodeGraph.counter
&& name.equals(nodeGraph.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name, counter);
}
@Override
public String toString() {
return "{"
+ "\"id\": \"" + id + '"'
+ ", \"name\": \"" + name + '"'
+ ", \"counter\":" + counter
+ '}';
}
}
/**
* Class for transport object that implements undirected graph edge.
*/
public static class EdgeGraph extends BaseDto {
/**
* Unique name of first graph node.
*/
@NotBlank(message = "nodeOne: " + VALIDATOR_NODE_NOT_BLANK_NAME_MESSAGE)
@Size(min = MIN_NAME_SIZE, max = MAX_NAME_SIZE)
private String nodeOne;
/**
* Unique name of second graph node.
*/
@NotBlank(message = "nodeTwo: " + VALIDATOR_NODE_NOT_BLANK_NAME_MESSAGE)
@Size(min = MIN_NAME_SIZE, max = MAX_NAME_SIZE)
private String nodeTwo;
/**
* Default ctor.
*/
public EdgeGraph() {
}
/**
* Ctor.
* @param edge - object of undirected graph edge.
*/
public EdgeGraph(final EdgeGraph edge) {
this(edge.getId(), edge.getNodeOne(), edge.getNodeTwo());
}
/**
* Ctor.
* @param nodeOne - unique name of first graph node.
* @param nodeTwo - unique name of second graph node.
*/
public EdgeGraph(final String nodeOne, final String nodeTwo) {
this(null, nodeOne, nodeTwo);
}
/**
* Ctor.
* @param nodeOne - object of first graph node.
* @param nodeTwo - object of second graph node.
*/
public EdgeGraph(final NodeGraph nodeOne, final NodeGraph nodeTwo) {
this(null, nodeOne.getName(), nodeTwo.getName());
}
/**
* Ctor.
* @param id - identity number of graph edge.
* @param nodeOne - unique name of first graph node.
* @param nodeTwo - unique name of second graph node.
*/
public EdgeGraph(final Integer id, final String nodeOne, final String nodeTwo) {
super(id);
this.nodeOne = nodeOne;
this.nodeTwo = nodeTwo;
}
/**
* Ctor.
* @param id - identity number of graph edge.
* @param nodeOne - object of first graph node.
* @param nodeTwo - object of second graph node.
*/
public EdgeGraph(final Integer id, final NodeGraph nodeOne, final NodeGraph nodeTwo) {
super(id);
this.nodeOne = nodeOne.getName();
this.nodeTwo = nodeTwo.getName();
}
public String getNodeOne() {
return nodeOne;
}
public void setNodeOne(final String nodeOne) {
this.nodeOne = nodeOne;
}
public String getNodeTwo() {
return nodeTwo;
}
public void setNodeTwo(final String nodeTwo) {
this.nodeTwo = nodeTwo;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EdgeGraph edgeGraph = (EdgeGraph) o;
return nodeOne.equals(edgeGraph.nodeOne)
&& nodeTwo.equals(edgeGraph.nodeTwo)
&& Objects.equals(id, edgeGraph.id);
}
@Override
public int hashCode() {
return Objects.hash(id, nodeOne, nodeTwo);
}
@Override
public String toString() {
return "{"
+ "\"id\": \"" + id + '"'
+ ", \"nodeOne\": \""
+ nodeOne + '"'
+ ", \"nodeTwo\": \""
+ nodeTwo + '"'
+ '}';
}
}
/**
* Collection of graph nodes.
*/
private Set<@Valid NodeGraph> nodes = new HashSet<>();
/**
* Collection of graph edges.
*/
private Set<@Valid EdgeGraph> edges = new HashSet<>();
/**
* Default ctor.
*/
public GraphDto() {
}
/**
* Ctor.
* @param nodes - collection of graph nodes {@link NodeGraph}.
* @param edges - collection of graph edges {@link EdgeGraph}.
*/
public GraphDto(final Set<NodeGraph> nodes, final Set<EdgeGraph> edges) {
this.nodes = nodes;
this.edges = edges;
}
public Set<NodeGraph> getNodes() {
return nodes;
}
public void setNodes(final Set<NodeGraph> nodes) {
this.nodes = nodes;
}
public Set<EdgeGraph> getEdges() {
return edges;
}
public void setEdges(final Set<EdgeGraph> edges) {
this.edges = edges;
}
@Override
public final String toString() {
return "{"
+ "\"nodes\": "
+ nodes
+ ", \"edges\": "
+ edges
+ '}';
}
}

View File

@@ -0,0 +1,92 @@
package ru.resprojects.linkchecker.model;
import org.hibernate.Hibernate;
import ru.resprojects.linkchecker.HasId;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.SequenceGenerator;
import java.io.Serializable;
/**
* Abstract base class for database entity.
*/
@MappedSuperclass
public abstract class AbstractBaseEntity implements HasId, Serializable {
/**
*
*/
private static final long serialVersionUID = -5006383911943128194L;
/**
* Initial number for database id sequential.
*/
private static final int START_SEQ = 5000;
/**
* Unique identity for database entity.
*/
@Id
@SequenceGenerator(name = "global_seq", sequenceName = "global_seq",
allocationSize = 1, initialValue = START_SEQ)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq")
protected Integer id;
/**
* Default ctor.
*/
AbstractBaseEntity() {
}
/**
* Ctor.
* @param id - unique database entity identity.
*/
AbstractBaseEntity(final Integer id) {
this.id = id;
}
/**
* Get unique database entity identity.
* @return unique database entity identity.
*/
@Override
public Integer getId() {
return id;
}
/**
* Set unique database entity identity.
* @param id of database entity.
*/
@Override
public void setId(final Integer id) {
this.id = id;
}
@Override
public String toString() {
return String.format("Entity %s (%s)", getClass().getName(), id);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || !getClass().equals(Hibernate.getClass(o))) {
return false;
}
AbstractBaseEntity that = (AbstractBaseEntity) o;
return id != null && id.equals(that.id);
}
@Override
public int hashCode() {
return id == null ? 0 : id;
}
}

View File

@@ -0,0 +1,67 @@
package ru.resprojects.linkchecker.model;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import static ru.resprojects.linkchecker.util.ValidationUtil.*;
/**
* Abstract base class for named database entity.
*/
@MappedSuperclass
public abstract class AbstractNamedEntity extends AbstractBaseEntity {
/**
*
*/
private static final long serialVersionUID = 6679903940869239340L;
/**
* Unique name of database entity.
*/
@Column(name = "name", nullable = false, unique = true)
@NotBlank(message = VALIDATOR_NODE_NOT_BLANK_NAME_MESSAGE)
@Size(min = MIN_NAME_SIZE, max = MAX_NAME_SIZE,
message = VALIDATOR_NODE_NAME_RANGE_MESSAGE)
private String name;
/**
* Default ctor.
*/
AbstractNamedEntity() {
}
/**
* Ctor.
* @param id of database entity.
* @param name unique name of database entity.
*/
AbstractNamedEntity(final Integer id, final String name) {
super(id);
this.name = name;
}
/**
* Set unique name of database entity.
* @param name unique name of database entity.
*/
public void setName(final String name) {
this.name = name;
}
/**
* Get unique name of database entity.
* @return unique name of database entity.
*/
public String getName() {
return this.name;
}
@Override
public String toString() {
return String.format("Entity %s (%s, '%s')",
getClass().getName(), getId(), name);
}
}

View File

@@ -0,0 +1,108 @@
package ru.resprojects.linkchecker.model;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import ru.resprojects.linkchecker.util.ValidationUtil;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotNull;
@Entity
@Table(name = "edges", uniqueConstraints = {
@UniqueConstraint(
columnNames = {"nodeone", "nodetwo"},
name = "unique_edge"
)
})
public class Edge extends AbstractBaseEntity {
/**
*
*/
private static final long serialVersionUID = -5267484684381196999L;
/**
* First object of graph node.
*/
@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "nodeone", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@NotNull(message = ValidationUtil.VALIDATOR_NOT_NULL_MESSAGE)
private Node nodeOne;
/**
* Second object of graph node.
*/
@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "nodetwo", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@NotNull(message = ValidationUtil.VALIDATOR_NOT_NULL_MESSAGE)
private Node nodeTwo;
/**
* Default ctor.
*/
public Edge() {
}
/**
* Ctor.
* @param edge - object of undirected graph edge.
*/
public Edge(final Edge edge) {
this(edge.getId(), edge.getNodeOne(), edge.getNodeTwo());
}
/**
* Ctor.
* @param nodeOne - first object of graph node.
* @param nodeTwo - second object of graph node.
*/
public Edge(final Node nodeOne, final Node nodeTwo) {
this(null, nodeOne, nodeTwo);
}
/**
* Ctor.
* @param id - unique identity for database graph edge entity.
* @param nodeOne - first object of graph node.
* @param nodeTwo - second object of graph node.
*/
public Edge(final Integer id, final Node nodeOne, final Node nodeTwo) {
this.id = id;
this.nodeOne = nodeOne;
this.nodeTwo = nodeTwo;
}
public Node getNodeOne() {
return nodeOne;
}
public void setNodeOne(final Node nodeOne) {
this.nodeOne = nodeOne;
}
public Node getNodeTwo() {
return nodeTwo;
}
public void setNodeTwo(final Node nodeTwo) {
this.nodeTwo = nodeTwo;
}
@Override
public String toString() {
return "Edge{"
+ "id=" + getId()
+ ", nodeOne="
+ nodeOne
+ ", nodeTwo="
+ nodeTwo
+ '}';
}
}

View File

@@ -0,0 +1,82 @@
package ru.resprojects.linkchecker.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import static ru.resprojects.linkchecker.util.ValidationUtil.*;
@Entity
@Table(name = "nodes", uniqueConstraints = {
@UniqueConstraint(columnNames = "name", name = "nodes_unique_name_idx")
})
public class Node extends AbstractNamedEntity {
/**
*
*/
private static final long serialVersionUID = 5592849608806842551L;
private static final String DEFAULT_COUNTER_VALUE = "int default " +
NODE_COUNTER_DEFAULT;
/**
* The number of passes through the node of the graph.
*/
@Column(name = "counter", columnDefinition = DEFAULT_COUNTER_VALUE)
private int counter;
/**
* Default ctor.
*/
public Node() {
}
/**
* Ctor.
* @param node - node of the graph database entity.
*/
public Node(final Node node) {
this(node.getId(), node.getName(), node.getCounter());
}
/**
* Ctor.
* @param name - unique name of database entity that implement graph node.
*/
public Node(final String name) {
super(null, name);
this.counter = NODE_COUNTER_DEFAULT;
}
/**
* Ctor.
* @param id - unique identity for database graph node entity.
* @param name - unique name of database entity that implement graph node.
* @param counter - number of passes through the node of the graph.
*/
public Node(final Integer id, final String name, final int counter) {
super(id, name);
this.counter = counter;
}
public int getCounter() {
return counter;
}
public void setCounter(final int counter) {
this.counter = counter;
}
@Override
public String toString() {
return "Node{"
+ "id=" + getId()
+ ", name='" + getName() + '\''
+ ", counter=" + counter
+ '}';
}
}

View File

@@ -0,0 +1,36 @@
package ru.resprojects.linkchecker.repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
import ru.resprojects.linkchecker.model.Edge;
import ru.resprojects.linkchecker.model.Node;
import java.util.List;
import java.util.Optional;
@Transactional(readOnly = true)
public interface EdgeRepository extends JpaRepository<Edge, Integer> {
@Transactional
<S extends Edge> S save(S edge);
@Transactional
<S extends Edge> List<S> saveAll(Iterable<S> entities);
@Transactional
void deleteById(int id);
@Transactional
void deleteAllInBatch();
@Transactional
void deleteInBatch(Iterable<Edge> entities);
Optional<Edge> findEdgeByNodeOneAndNodeTwo(Node nodeOne, Node nodeTwo);
List<Edge> findEdgesByNodeOneOrNodeTwo(Node nodeOne, Node nodeTwo);
boolean existsById(int id);
}

View File

@@ -0,0 +1,30 @@
package ru.resprojects.linkchecker.repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
import ru.resprojects.linkchecker.model.Node;
import java.util.List;
@Transactional(readOnly = true)
public interface NodeRepository extends JpaRepository<Node, Integer> {
@Transactional
<S extends Node> S save(S node);
@Transactional
<S extends Node> List<S> saveAll(Iterable<S> entities);
@Transactional
void deleteById(Integer id);
@Transactional
void deleteByName(String name);
@Transactional
void deleteAllInBatch();
Node getByName(String name);
boolean existsByName(String name);
}

View File

@@ -0,0 +1,98 @@
package ru.resprojects.linkchecker.services;
import ru.resprojects.linkchecker.model.Edge;
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
import java.util.Set;
import static ru.resprojects.linkchecker.dto.GraphDto.EdgeGraph;
/**
* GraphEdgeService - the interface for work with graph edges.
*/
public interface GraphEdgeService {
/**
* Create edge of the graph. Nodes that linked by the edge, must be
* exist in graph, else throw exception.
* @param edgeGraph edge {@link EdgeGraph} of the graph.
* @return added graph edge.
* @throws NotFoundException while creating edge
*/
EdgeGraph create(final EdgeGraph edgeGraph) throws NotFoundException;
/**
* Batch creation edges of the graph. Nodes that linked by the edge, must be
* exist in graph, else throw exception.
* @param edgeGraphs set of graph edges {@link EdgeGraph}
* @return added graph edges.
* @throws NotFoundException while creating edges
*/
Set<EdgeGraph> create(final Set<EdgeGraph> edgeGraphs) throws NotFoundException;
/**
* Search edge of the graph by id and delete from graph.
* @param id of edge of the graph.
* @throws NotFoundException if edge is not found in the graph.
*/
void delete(final Integer id) throws NotFoundException;
/**
* Search edges by unique name of the graph node and delete them from graph.
* Since the edge describes the connection of two nodes, the search occurs
* on the node one or node two.
* @param nodeName unique name of the graph node.
* @throws NotFoundException if edge is not found in the graph.
*/
void delete(final String nodeName) throws NotFoundException;
/**
* Search edge of the graph by node one and node two and if edge contain
* both these nodes then delete edge from graph.
* @param nodeNameOne unique name of the first graph node.
* @param nodeNameTwo unique name of the second graph node.
* @throws NotFoundException if edge is not found in the graph.
*/
void delete(final String nodeNameOne, final String nodeNameTwo) throws NotFoundException;
void delete(final Set<Edge> edges) throws NotFoundException;
/**
* Removing all edges from the graph.
*/
void deleteAll();
/**
* Search edge of the graph by node one and node two and if edge contain
* both these nodes then return edge else throw exception.
* @param nodeNameOne unique name of the first graph node.
* @param nodeNameTwo unique name of the second graph node.
* @return graph edge {@link EdgeGraph}.
* @throws NotFoundException if edge is not found in the graph.
*/
EdgeGraph get(final String nodeNameOne, final String nodeNameTwo) throws NotFoundException;
/**
* Search edges by unique name of the graph node and return it.
* Since the edge describes the connection of two nodes, the search occurs
* on the node one or node two.
* @param nodeName unique name of the graph node.
* @return set of edges of the graph
*/
Set<EdgeGraph> get(final String nodeName);
/**
* Get edge of the graph by id.
* @param id of edge ot the graph.
* @return edge {@link EdgeGraph} of the graph.
* @throws NotFoundException if edge is not found in the graph.
*/
EdgeGraph getById(final Integer id) throws NotFoundException;
/**
* Get all edges from graph.
* @return set of edges ot the graph.
*/
Set<EdgeGraph> getAll();
}

View File

@@ -0,0 +1,232 @@
package ru.resprojects.linkchecker.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import ru.resprojects.linkchecker.AppProperties;
import ru.resprojects.linkchecker.model.Edge;
import ru.resprojects.linkchecker.model.Node;
import ru.resprojects.linkchecker.repositories.EdgeRepository;
import ru.resprojects.linkchecker.repositories.NodeRepository;
import ru.resprojects.linkchecker.util.GraphUtil;
import ru.resprojects.linkchecker.util.exeptions.ApplicationException;
import ru.resprojects.linkchecker.util.exeptions.ErrorPlaceType;
import ru.resprojects.linkchecker.util.exeptions.ErrorType;
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static ru.resprojects.linkchecker.dto.GraphDto.EdgeGraph;
import static ru.resprojects.linkchecker.util.ValidationUtil.checkNotFound;
@Service
public class GraphEdgeServiceImpl implements GraphEdgeService {
private final EdgeRepository edgeRepository;
private final NodeRepository nodeRepository;
private final AppProperties properties;
@Autowired
public GraphEdgeServiceImpl(final EdgeRepository edgeRepository,
final NodeRepository nodeRepository, final AppProperties properties) {
this.edgeRepository = edgeRepository;
this.nodeRepository = nodeRepository;
this.properties = properties;
}
private boolean isPresent(final Node nodeOne, final Node nodeTwo) {
try {
get(nodeOne.getName(), nodeTwo.getName());
return true;
} catch (NotFoundException e) {
return false;
}
}
@Override
public EdgeGraph create(final EdgeGraph edgeGraph) throws NotFoundException {
if (Objects.isNull(edgeGraph)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.EDGE,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_ARGUMENT_NULL")
);
}
Node nodeOne = checkNotFound(
nodeRepository.getByName(edgeGraph.getNodeOne()),
String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), edgeGraph.getNodeOne()),
ErrorPlaceType.EDGE
);
Node nodeTwo = checkNotFound(
nodeRepository.getByName(edgeGraph.getNodeTwo()),
String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), edgeGraph.getNodeTwo()),
ErrorPlaceType.EDGE
);
if (isPresent(nodeOne, nodeTwo)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.EDGE,
HttpStatus.UNPROCESSABLE_ENTITY,
String.format(
properties.getEdgeMsg().get("EDGE_MSG_ALREADY_PRESENT_ERROR"),
nodeOne.getName(),
nodeTwo.getName(),
nodeTwo.getName(),
nodeOne.getName()
)
);
}
Edge edge = new Edge(nodeOne, nodeTwo);
return GraphUtil.edgeToEdgeGraph(edgeRepository.save(edge));
}
@Override
public Set<EdgeGraph> create(final Set<EdgeGraph> edgeGraphs) throws NotFoundException {
if (Objects.isNull(edgeGraphs)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.EDGE,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_ARGUMENT_NULL")
);
}
if (edgeGraphs.isEmpty()) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.EDGE,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_COLLECTION_EMPTY")
);
}
Map<EdgeGraph, Map<String, Node>> nodes = new HashMap<>();
for (EdgeGraph edgeGraph : edgeGraphs) {
if (Objects.isNull(edgeGraph)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.EDGE,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_COLLECTION_CONTAIN_NULL")
);
}
Node nodeOne = checkNotFound(
nodeRepository.getByName(edgeGraph.getNodeOne()),
String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), edgeGraph.getNodeOne()),
ErrorPlaceType.EDGE
);
Node nodeTwo = checkNotFound(
nodeRepository.getByName(edgeGraph.getNodeTwo()),
String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), edgeGraph.getNodeTwo()),
ErrorPlaceType.EDGE
);
if (isPresent(nodeOne, nodeTwo)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.EDGE,
HttpStatus.UNPROCESSABLE_ENTITY,
String.format(
properties.getEdgeMsg().get("EDGE_MSG_ALREADY_PRESENT_ERROR"),
nodeOne.getName(),
nodeTwo.getName(),
nodeTwo.getName(),
nodeOne.getName()
)
);
}
Map<String, Node> nodeMap = new HashMap<>();
nodeMap.put(edgeGraph.getNodeOne(), nodeOne);
nodeMap.put(edgeGraph.getNodeTwo(), nodeTwo);
nodes.put(edgeGraph, nodeMap);
}
List<Edge> edges = edgeGraphs.stream()
.map(eg -> new Edge(
nodes.get(eg).get(eg.getNodeOne()),
nodes.get(eg).get(eg.getNodeTwo()))
).collect(Collectors.toList());
return GraphUtil.edgesToEdgeGraphs(edgeRepository.saveAll(edges));
}
@Override
public void delete(final Integer id) throws NotFoundException {
if (edgeRepository.existsById(id)) {
edgeRepository.deleteById(id);
} else {
throw new NotFoundException(String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"), ErrorPlaceType.EDGE, id), ErrorPlaceType.EDGE);
}
}
@Override
public void delete(final String nodeName) throws NotFoundException {
List<Edge> edges = getEdges(nodeName);
if (edges.isEmpty()) {
throw new NotFoundException(String.format(properties.getEdgeMsg().get("EDGE_MSG_GET_BY_NAME_ERROR"), nodeName), ErrorPlaceType.EDGE);
}
edgeRepository.deleteInBatch(edges);
}
@Override
public void delete(final String nodeNameOne, final String nodeNameTwo) throws NotFoundException {
Edge edge = checkNotFound(getEdge(nodeNameOne, nodeNameTwo),
String.format(properties.getEdgeMsg().get("EDGE_MSG_GET_ERROR"), nodeNameOne, nodeNameTwo),
ErrorPlaceType.EDGE);
edgeRepository.delete(edge);
}
@Override
public void delete(Set<Edge> edges) throws NotFoundException {
edgeRepository.deleteInBatch(edges);
}
@Override
public void deleteAll() {
edgeRepository.deleteAllInBatch();
}
@Override
public EdgeGraph get(final String nodeNameOne, final String nodeNameTwo) throws NotFoundException {
EdgeGraph edgeGraph = GraphUtil.edgeToEdgeGraph(getEdge(nodeNameOne, nodeNameTwo));
return checkNotFound(edgeGraph,
String.format(properties.getEdgeMsg().get("EDGE_MSG_GET_ERROR"), nodeNameOne, nodeNameTwo),
ErrorPlaceType.EDGE);
}
private Edge getEdge(final String nodeNameOne, final String nodeNameTwo) {
Node nodeOne = nodeRepository.getByName(nodeNameOne);
Node nodeTwo = nodeRepository.getByName(nodeNameTwo);
return edgeRepository.findEdgeByNodeOneAndNodeTwo(nodeOne, nodeTwo)
.orElse(edgeRepository.findEdgeByNodeOneAndNodeTwo(nodeTwo, nodeOne).orElse(null));
}
@Override
public Set<EdgeGraph> get(final String nodeName) {
List<Edge> result = getEdges(nodeName);
if (result.isEmpty()) {
throw new NotFoundException(String.format(properties.getEdgeMsg()
.get("EDGE_MSG_GET_BY_NAME_ERROR"), nodeName), ErrorPlaceType.EDGE);
}
return GraphUtil.edgesToEdgeGraphs(result);
}
private List<Edge> getEdges(final String nodeName) {
Node node = nodeRepository.getByName(nodeName);
return edgeRepository.findEdgesByNodeOneOrNodeTwo(node, node);
}
@Override
public EdgeGraph getById(final Integer id) throws NotFoundException {
EdgeGraph edgeGraph = GraphUtil.edgeToEdgeGraph(edgeRepository.findById(id)
.orElse(null));
return checkNotFound(edgeGraph, String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"),
ErrorPlaceType.EDGE, id), ErrorPlaceType.EDGE);
}
@Override
public Set<EdgeGraph> getAll() {
return GraphUtil.edgesToEdgeGraphs(edgeRepository.findAll());
}
}

View File

@@ -0,0 +1,85 @@
package ru.resprojects.linkchecker.services;
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
import java.util.Set;
import static ru.resprojects.linkchecker.dto.GraphDto.NodeGraph;
/**
* GraphNodeService - the interface for work with graph nodes.
*/
public interface GraphNodeService {
/**
* Create node of the graph.
* @param nodeGraph graph node {@link NodeGraph}
* @return added graph node.
*/
NodeGraph create(final NodeGraph nodeGraph);
/**
* Batch creation nodes of the graph
* @param nodeGraphs set of graph nodes {@link NodeGraph}
* @return added graph nodes.
*/
Set<NodeGraph> create(final Set<NodeGraph> nodeGraphs);
/**
* Update node of the graph.
* @param nodeGraph graph node {@link NodeGraph}
* @throws NotFoundException if node not updated
*/
void update(final NodeGraph nodeGraph) throws NotFoundException;
/**
* Search graph node by id and delete from graph.
* @param id of node of the graph.
* @throws NotFoundException if node not found in the graph.
*/
void delete(final Integer id) throws NotFoundException;
/**
* Search graph node by unique name and delete from graph.
* @param name unique name of graph node.
* @throws NotFoundException if node not found in the graph.
*/
void delete(final String name) throws NotFoundException;
/**
* Search graph node by object {@link NodeGraph} and delete from graph.
* @param nodeGraph object {@link NodeGraph}.
* @throws NotFoundException if node not found in the graph.
*/
void delete(final NodeGraph nodeGraph) throws NotFoundException;
/**
* Will be deleted all nodes and edges associated with
* these nodes in the graph.
*/
void deleteAll();
/**
* Search and return graph node by unique name.
* @param name unique name of graph node.
* @return node of the graph.
* @throws NotFoundException if node not found in the graph.
*/
NodeGraph get(final String name) throws NotFoundException;
/**
* Search and return graph node by id.
* @param id of node of the graph.
* @return node of the graph.
* @throws NotFoundException if node not found in the graph.
*/
NodeGraph getById(final Integer id) throws NotFoundException;
/**
* Get all graph nodes.
* @return set of graph nodes.
*/
Set<NodeGraph> getAll();
}

View File

@@ -0,0 +1,207 @@
package ru.resprojects.linkchecker.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import ru.resprojects.linkchecker.AppProperties;
import ru.resprojects.linkchecker.repositories.NodeRepository;
import ru.resprojects.linkchecker.util.GraphUtil;
import ru.resprojects.linkchecker.util.exeptions.ApplicationException;
import ru.resprojects.linkchecker.util.exeptions.ErrorPlaceType;
import ru.resprojects.linkchecker.util.exeptions.ErrorType;
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
import java.util.Objects;
import java.util.Set;
import static ru.resprojects.linkchecker.dto.GraphDto.NodeGraph;
import static ru.resprojects.linkchecker.util.ValidationUtil.checkNotFound;
@Service
public class GraphNodeServiceImpl implements GraphNodeService {
private final NodeRepository nodeRepository;
private final AppProperties properties;
@Autowired
public GraphNodeServiceImpl(final NodeRepository nodeRepository, final AppProperties properties) {
this.nodeRepository = nodeRepository;
this.properties = properties;
}
private boolean isPresent(final NodeGraph nodeGraph) {
try {
get(nodeGraph.getName());
return true;
} catch (NotFoundException e) {
return false;
}
}
@Override
public NodeGraph create(final NodeGraph nodeGraph) {
if (Objects.isNull(nodeGraph)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.NODE,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_ARGUMENT_NULL")
);
}
if (isPresent(nodeGraph)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.NODE,
HttpStatus.UNPROCESSABLE_ENTITY,
String.format(
properties.getNodeMsg().get("NODE_MSG_ALREADY_PRESENT_ERROR"),
nodeGraph.getName()
)
);
}
return GraphUtil.nodeToNodeGraph(nodeRepository.save(
GraphUtil.nodeGraphToNode(nodeGraph)));
}
@Override
public Set<NodeGraph> create(Set<NodeGraph> nodeGraphs) {
if (Objects.isNull(nodeGraphs)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.NODE,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_ARGUMENT_NULL")
);
}
if (nodeGraphs.isEmpty()) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.NODE,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_COLLECTION_EMPTY")
);
}
nodeGraphs.forEach(nodeGraph -> {
if (Objects.isNull(nodeGraph)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.NODE,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_COLLECTION_CONTAIN_NULL")
);
}
if (isPresent(nodeGraph)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.NODE,
HttpStatus.UNPROCESSABLE_ENTITY,
String.format(
properties.getNodeMsg().get("NODE_MSG_ALREADY_PRESENT_ERROR"),
nodeGraph.getName()
)
);
}
});
return GraphUtil.nodesToNodeGraphs(nodeRepository.saveAll(
GraphUtil.nodeGraphsToNodes(nodeGraphs)));
}
@Override
public void update(final NodeGraph nodeGraph) throws NotFoundException {
if (Objects.isNull(nodeGraph)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.NODE,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_ARGUMENT_NULL")
);
}
checkNotFound(nodeRepository.save(
GraphUtil.nodeGraphToNode(nodeGraph)),
properties.getNodeMsg().get("NODE_MSG_UPDATE_ERROR") + nodeGraph.getId(),
ErrorPlaceType.NODE
);
}
@Override
public void delete(final Integer id) throws NotFoundException {
if (nodeRepository.existsById(id)) {
nodeRepository.deleteById(id);
} else {
throw new NotFoundException(String.format(
properties.getAppMsg().get("MSG_BY_ID_ERROR"),
ErrorPlaceType.NODE, id
), ErrorPlaceType.NODE);
}
}
@Override
public void delete(final String name) throws NotFoundException {
if (nodeRepository.existsByName(name)) {
nodeRepository.deleteByName(name);
} else {
throw new NotFoundException(String.format(
properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"),
name
), ErrorPlaceType.NODE);
}
}
@Override
public void delete(final NodeGraph nodeGraph) throws NotFoundException {
if (Objects.isNull(nodeGraph)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.NODE,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_ARGUMENT_NULL")
);
}
try {
NodeGraph nodeFromRepo = GraphUtil.nodeToNodeGraph(nodeRepository
.findById(nodeGraph.getId()).orElse(null));
if (nodeGraph.equals(nodeFromRepo)) {
nodeRepository.deleteById(nodeGraph.getId());
} else {
throw new Exception();
}
} catch (Exception e) {
throw new NotFoundException(String.format(
properties.getNodeMsg().get("NODE_MSG_BY_OBJECT_ERROR"),
nodeGraph.toString()
), ErrorPlaceType.NODE);
}
}
@Override
public void deleteAll() {
nodeRepository.deleteAllInBatch();
}
@Override
public Set<NodeGraph> getAll() {
return GraphUtil.nodesToNodeGraphs(nodeRepository.findAll());
}
@Override
public NodeGraph get(final String name) throws NotFoundException {
NodeGraph nodeGraph = GraphUtil.nodeToNodeGraph(nodeRepository.getByName(name));
return checkNotFound(nodeGraph,
String.format(
properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"),
name
), ErrorPlaceType.NODE);
}
@Override
public NodeGraph getById(final Integer id) throws NotFoundException {
NodeGraph nodeGraph = GraphUtil.nodeToNodeGraph(nodeRepository
.findById(id).orElse(null));
return checkNotFound(nodeGraph,
String.format(
properties.getAppMsg().get("MSG_BY_ID_ERROR"),
ErrorPlaceType.NODE, id
), ErrorPlaceType.NODE);
}
}

View File

@@ -0,0 +1,60 @@
package ru.resprojects.linkchecker.services;
import ru.resprojects.linkchecker.dto.GraphDto;
import ru.resprojects.linkchecker.util.exeptions.ApplicationException;
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
import java.util.Set;
/**
* GraphService - the interface for work with <a href = https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)#Graph>undirected graph</a>.
*/
public interface GraphService {
/**
* Checking input graph data, removing cycles from input graph and saving it
* to the DB.
* @param graphTo graph {@link GraphDto}
* @return graph.
* @throws ApplicationException if found errors.
*/
GraphDto create(final GraphDto graphTo) throws ApplicationException;
/**
* Get graph.
* @return graph {@link GraphDto}
*/
GraphDto get();
/**
* Remove graph.
*/
void clear();
/**
* Checking route between nodes.
* @param nodeNameSet collection of the unique nodes name.
* @return check result in JSON format.
* @throws NotFoundException if route is not found
*/
String checkRoute(final Set<String> nodeNameSet) throws NotFoundException;
/**
* Exporting graph to <a href = https://www.graphviz.org/about/>graphviz</a> format.
* @return string data in graphviz format.
*/
String exportToGraphViz();
/**
* Get access to nodes of the graph.
* @return {@link GraphNodeService}
*/
GraphNodeService getNodes();
/**
* Get access to edges of the graph.
* @return {@link GraphEdgeService}
*/
GraphEdgeService getEdges();
}

View File

@@ -0,0 +1,215 @@
package ru.resprojects.linkchecker.services;
import org.jgrapht.Graph;
import org.jgrapht.alg.interfaces.ShortestPathAlgorithm;
import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
import org.jgrapht.graph.DefaultEdge;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import ru.resprojects.linkchecker.AppProperties;
import ru.resprojects.linkchecker.dto.GraphDto;
import ru.resprojects.linkchecker.model.AbstractNamedEntity;
import ru.resprojects.linkchecker.model.Edge;
import ru.resprojects.linkchecker.model.Node;
import ru.resprojects.linkchecker.util.GraphUtil;
import ru.resprojects.linkchecker.util.exeptions.ApplicationException;
import ru.resprojects.linkchecker.util.exeptions.ErrorPlaceType;
import ru.resprojects.linkchecker.util.exeptions.ErrorType;
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static ru.resprojects.linkchecker.dto.GraphDto.NodeGraph;
import static ru.resprojects.linkchecker.dto.GraphDto.EdgeGraph;
import static ru.resprojects.linkchecker.util.GraphUtil.*;
@Service
public class GraphServiceImpl implements GraphService {
private final GraphEdgeService edges;
private final GraphNodeService nodes;
private final AppProperties properties;
@Autowired
public GraphServiceImpl(final GraphEdgeService edges, final GraphNodeService nodes, final AppProperties properties) {
this.edges = edges;
this.nodes = nodes;
this.properties = properties;
}
@Override
public GraphDto create(final GraphDto graphTo) throws ApplicationException {
if (Objects.isNull(graphTo)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.GRAPH,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_ARGUMENT_NULL")
);
}
if (graphTo.getNodes().isEmpty() && !graphTo.getEdges().isEmpty()) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.GRAPH,
HttpStatus.UNPROCESSABLE_ENTITY,
"NODES: " + properties.getAppMsg().get("MSG_COLLECTION_EMPTY")
);
}
clear();
GraphDto graph = graphToGraphDto(
removeCyclesFromGraph(graphBuilder(graphTo.getNodes(), graphTo.getEdges())));
Set<NodeGraph> nodeGraphSet = nodes.create(graph.getNodes());
Set<EdgeGraph> edgeGraphSet = edges.create(graph.getEdges());
return new GraphDto(nodeGraphSet, edgeGraphSet);
}
@Override
public GraphDto get() {
return removeGraphCycles(new GraphDto(nodes.getAll(), edges.getAll()));
}
@Override
public String exportToGraphViz() {
return GraphUtil.exportToGraphViz(get());
}
@Override
public void clear() {
nodes.deleteAll();
}
@Override
public String checkRoute(final Set<String> nodeNameSet) throws NotFoundException {
if (Objects.isNull(nodeNameSet)) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.GRAPH,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_ARGUMENT_NULL")
);
}
if (nodeNameSet.isEmpty()) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.GRAPH,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_COLLECTION_EMPTY")
);
}
if (nodeNameSet.size() == 1) {
throw new ApplicationException(
ErrorType.DATA_ERROR,
ErrorPlaceType.GRAPH,
HttpStatus.UNPROCESSABLE_ENTITY,
properties.getAppMsg().get("MSG_COLLECTION_CONTAIN_ONE_ELEMENT")
);
}
GraphDto graphDto = removeGraphCycles(new GraphDto(nodes.getAll(),
edges.getAll()));
Map<String, Boolean> faultNodes = getRandomNodeFault(graphDto.getNodes());
List<String> nodeNameList = new ArrayList<>(nodeNameSet);
Graph<Node, DefaultEdge> graph = graphBuilder(graphDto.getNodes(),
graphDto.getEdges());
DijkstraShortestPath<Node, DefaultEdge> dAlg = new DijkstraShortestPath<>(graph);
Node firstNode = nodeGraphToNode(graphDto.getNodes().stream()
.filter(ng -> ng.getName().equalsIgnoreCase(nodeNameList.get(0)))
.findFirst()
.orElse(null));
if (Objects.isNull(firstNode)) {
throw new NotFoundException(
String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), nodeNameList.get(0)),
ErrorPlaceType.GRAPH
);
}
ShortestPathAlgorithm.SingleSourcePaths<Node, DefaultEdge> paths = dAlg.getPaths(firstNode);
if (faultNodes.getOrDefault(firstNode.getName(), false)) {
throw new NotFoundException(
String.format(properties.getNodeMsg().get("NODE_MSG_IS_FAULT"), firstNode.getName()),
ErrorPlaceType.GRAPH
);
}
nodeNameList.stream().skip(1).forEach(name -> {
Node nextNode = nodeGraphToNode(graphDto.getNodes().stream()
.filter(ng -> ng.getName().equalsIgnoreCase(name))
.findFirst()
.orElse(null));
if (Objects.isNull(nextNode)) {
throw new NotFoundException(
String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), name),
ErrorPlaceType.GRAPH
);
}
if (faultNodes.getOrDefault(nextNode.getName(), false)) {
throw new NotFoundException(
String.format(properties.getNodeMsg().get("NODE_MSG_IS_FAULT"), name),
ErrorPlaceType.GRAPH
);
}
List<Node> findNodes = paths.getPath(nextNode).getVertexList();
List<String> findNodesName = findNodes.stream()
.map(AbstractNamedEntity::getName)
.collect(Collectors.toList());
if (!nodeNameList.containsAll(findNodesName)) {
throw new NotFoundException(
String.format(properties.getNodeMsg().get("NODE_MSG_NOT_REACHABLE"), nodeNameList.get(0), name),
ErrorPlaceType.GRAPH
);
}
});
nodeNameList.forEach(name ->
graphDto.getNodes().stream()
.filter(ng -> ng.getName().equalsIgnoreCase(name))
.findFirst()
.ifPresent(ng -> {
ng.setCounter(ng.getCounter() + 1);
nodes.update(ng);
}));
return String.format("Route for nodes %s is found", nodeNameList.toString());
}
private GraphDto removeGraphCycles(final GraphDto graph) {
if (Objects.isNull(graph) || graph.getEdges().isEmpty()) {
return graph;
}
GraphDto optimizedGraph = graphToGraphDto(
removeCyclesFromGraph(graphBuilder(graph.getNodes(), graph.getEdges())));
//Checking, was removed edges or not from graph after optimizing
if (graph.getEdges().size() == optimizedGraph.getEdges().size()) {
return graph;
}
//Because ID's lost's in optimized graph, we need recover them.
Set<EdgeGraph> optimizedEdges = graph.getEdges().stream()
.filter(e -> optimizedGraph.getEdges().stream()
.anyMatch(eg -> eg.getNodeOne().equalsIgnoreCase(e.getNodeOne())
&& eg.getNodeTwo().equalsIgnoreCase(e.getNodeTwo())))
.collect(Collectors.toSet());
//Rewrite edge collection in optimized graph.
optimizedGraph.setEdges(optimizedEdges);
//Search all edges that was removed from graph and remove them from DB.
Set<EdgeGraph> removedEdgesGraph = graph.getEdges().stream()
.filter(eg -> !optimizedGraph.getEdges().contains(eg))
.collect(Collectors.toSet());
Set<Edge> removedEdges = getEdgesFromGraphDto(new GraphDto(
optimizedGraph.getNodes(),
removedEdgesGraph));
if (!removedEdges.isEmpty()) {
edges.delete(removedEdges);
return optimizedGraph;
}
return graph;
}
public GraphEdgeService getEdges() {
return edges;
}
public GraphNodeService getNodes() {
return nodes;
}
}

View File

@@ -0,0 +1,348 @@
package ru.resprojects.linkchecker.util;
import org.jgrapht.Graph;
import org.jgrapht.GraphPath;
import org.jgrapht.Graphs;
import org.jgrapht.alg.cycle.PatonCycleBase;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.SimpleGraph;
import org.jgrapht.io.ComponentNameProvider;
import org.jgrapht.io.DOTExporter;
import org.jgrapht.io.GraphExporter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.resprojects.linkchecker.dto.GraphDto;
import ru.resprojects.linkchecker.model.Edge;
import ru.resprojects.linkchecker.model.Node;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import static ru.resprojects.linkchecker.dto.GraphDto.NodeGraph;
import static ru.resprojects.linkchecker.dto.GraphDto.EdgeGraph;
/**
* Helper class for work with graph.
*/
public class GraphUtil {
private static final Logger LOG = LoggerFactory.getLogger(GraphUtil.class);
/**
* Ctor.
*/
private GraphUtil() {
}
/**
* Building graph in <a href = https://github.com/jgrapht/jgrapht/blob/master/README.md>JGraphT</a> format
* from collections of the nodes and edges.
* @param nodesGraph collection of nodes {@link NodeGraph}.
* @param edgesGraph collection of edges {@link EdgeGraph}.
* @return graph in JGraphT format.
*/
public static Graph<Node, DefaultEdge> graphBuilder(final Collection<NodeGraph> nodesGraph,
final Collection<EdgeGraph> edgesGraph) {
Graph<Node, DefaultEdge> graph = new SimpleGraph<>(DefaultEdge.class);
if (Objects.isNull(nodesGraph) || Objects.isNull(edgesGraph)) {
LOG.debug("graphBuilder: Return empty graph because one of the input collection is null");
return graph;
}
Set<Node> nodes = nodeGraphsToNodes(nodesGraph);
Set<Edge> edges = edgesGraph.stream()
.filter(Objects::nonNull)
.map(eg -> {
Node nodeOne = nodes.stream()
.filter(n -> n.getName().equalsIgnoreCase(eg.getNodeOne()))
.findFirst()
.orElse(null);
Node nodeTwo = nodes.stream()
.filter(n -> n.getName().equalsIgnoreCase(eg.getNodeTwo()))
.findFirst()
.orElse(null);
if (Objects.nonNull(nodeOne) && Objects.nonNull(nodeTwo)) {
return new Edge(eg.getId(), nodeOne, nodeTwo);
} else {
return null;
} })
.filter(Objects::nonNull)
.collect(Collectors.toSet());
nodes.forEach(graph::addVertex);
edges.forEach(edge -> graph.addEdge(
edge.getNodeOne(), edge.getNodeTwo()
));
return graph;
}
/**
* Convert graph from {@see <a href = https://github.com/jgrapht/jgrapht/blob/master/README.md>JGraphT</a>} format
* to graph DTO {@link GraphDto} format.
* ATTENTION: GraphDto will return edges without IDs,
* because graph in JGraphT format does not store IDs for edges!
* @param graph graph in JGraphT format.
* @return {@link GraphDto}.
*/
public static GraphDto graphToGraphDto(final Graph<Node, DefaultEdge> graph) {
if (Objects.isNull(graph)) {
LOG.debug("graphToGraphDto: returned empty DTO graph because input graph is null.");
return new GraphDto();
}
return new GraphDto(getNodesDtoFromGraph(graph), getEdgesDtoFromGraph(graph));
}
/**
* Converting JGraphT nodes to the GraphDto nodes {@link NodeGraph}
* @param graph graph in the JGraphT format.
* @return collection of GraphDto nodes.
*/
private static Set<NodeGraph> getNodesDtoFromGraph(final Graph<Node, DefaultEdge> graph) {
if (Objects.isNull(graph)) {
LOG.debug("getNodesDtoFromGraph: returned empty collection, because input graph is null");
return new HashSet<>();
}
return graph.vertexSet().stream()
.map(n -> new NodeGraph(n.getId(), n.getName(), n.getCounter()))
.collect(Collectors.toSet());
}
/**
* Converting JGraphT edges to the GraphDto edges {@link NodeGraph}
* @param graph graph in the JGraphT format.
* @return collection of GraphDto edges without IDs.
*/
private static Set<EdgeGraph> getEdgesDtoFromGraph(final Graph<Node, DefaultEdge> graph) {
if (Objects.isNull(graph)) {
LOG.debug("getEdgesDtoFromGraph: returned empty collection, because input graph is null");
return new HashSet<>();
}
return graph.edgeSet().stream()
.map(e -> new EdgeGraph(graph.getEdgeSource(e).getName(), graph.getEdgeTarget(e).getName()))
.collect(Collectors.toSet());
}
/**
* Find a cycle basis of an undirected graph using a variant of Paton's
* algorithm from {@see <a href = https://github.com/jgrapht/jgrapht/blob/master/README.md>JGraphT</a>} library.
* NOTE: while removing cycles algorithm each time returns a random set of
* graph edges for the same graph.
* @param graph graph in JGraphT format.
* @return graph in JGraphT format without cycles.
*/
public static Graph<Node, DefaultEdge> removeCyclesFromGraph(
final Graph<Node, DefaultEdge> graph) {
LOG.debug("removeCyclesFromGraph: Detect cycles in graph by Paton algorithm");
if (!isGraphContainCycles(graph)) {
LOG.debug("removeCyclesFromGraph: Cycles not found!");
return graph;
}
SimpleGraph<Node, DefaultEdge> result = new SimpleGraph<>(DefaultEdge.class);
Graphs.addGraph(result, graph);
while (true) {
LOG.debug("removeCyclesFromGraph: Try detect cycles");
PatonCycleBase<Node, DefaultEdge> patonCycleBase = new PatonCycleBase<>(result);
Set<GraphPath<Node, DefaultEdge>> paths = patonCycleBase.getCycleBasis().getCyclesAsGraphPaths();
Set<DefaultEdge> edgeSet = new HashSet<>();
if (paths.size() != 0) {
LOG.debug("removeCyclesFromGraph: Cycles found! Count of cycles in present graph = {}", paths.size());
for (GraphPath<Node, DefaultEdge> graphPath : paths) {
edgeSet.addAll(graphPath.getEdgeList());
}
LOG.debug("removeCyclesFromGraph: Remove edge from cycle");
if (edgeSet.iterator().hasNext()) {
DefaultEdge edge = edgeSet.iterator().next();
result.removeEdge(edge);
}
} else {
LOG.debug("removeCyclesFromGraph: Cycles not found!");
break;
}
}
return result;
}
private static boolean isGraphContainCycles(final Graph<Node, DefaultEdge> graph) {
SimpleGraph<Node, DefaultEdge> result = new SimpleGraph<>(DefaultEdge.class);
Graphs.addGraph(result, graph);
PatonCycleBase<Node, DefaultEdge> patonCycleBase = new PatonCycleBase<>(result);
Set<GraphPath<Node, DefaultEdge>> paths = patonCycleBase.getCycleBasis()
.getCyclesAsGraphPaths();
return !paths.isEmpty();
}
/**
* Export graph to {@see <a href = https://www.graphviz.org/ >GraphViz.dot</a>}
* format.
* @param graphTo graph in DTO format, see {@link GraphDto}.
* @return graph in GraphViz.dot format.
*/
public static String exportToGraphViz(final GraphDto graphTo) {
if (Objects.isNull(graphTo)) return "";
Graph<Node, DefaultEdge> graph = graphBuilder(graphTo.getNodes(), graphTo.getEdges());
ComponentNameProvider<Node> vertexIdProvider = component ->
component.getName() + "_" + component.getId();
ComponentNameProvider<Node> vertexNameProvider = Node::getName;
GraphExporter<Node, DefaultEdge> exporter = new DOTExporter<>(
vertexIdProvider, vertexNameProvider, null
);
try (Writer writer = new StringWriter()) {
exporter.exportGraph(graph, writer);
LOG.debug(writer.toString());
return writer.toString();
} catch (Exception e) {
LOG.warn("Fail export graph to GraphViz format.", e);
}
return "";
}
/**
* Converting collection of {@link Node} into set of {@link NodeGraph}
* @param nodes node model objects collection.
* @return collection of graph node DTO or empty collection if nodes is null.
*/
public static Set<NodeGraph> nodesToNodeGraphs(final Collection<Node> nodes) {
if (Objects.isNull(nodes)) {
LOG.debug("nodesToNodeGraphs: return empty collection because input collection of nodes is null");
return new HashSet<>();
}
return nodes.stream()
.filter(Objects::nonNull)
.map(GraphUtil::nodeToNodeGraph)
.collect(Collectors.toSet());
}
/**
* Converting collection of {@link NodeGraph} to collection of {@link Node}
* @param nodeGraphs collection of graph node DTO
* @return collection of node model objects or empty collection if
* nodeGraphs is null.
*/
public static Set<Node> nodeGraphsToNodes(final Collection<NodeGraph> nodeGraphs) {
if (Objects.isNull(nodeGraphs)) {
LOG.debug("nodeGraphsToNodes: return empty collection because input collection of DTO nodes is null");
return new HashSet<>();
}
return nodeGraphs.stream()
.filter(Objects::nonNull)
.map(GraphUtil::nodeGraphToNode)
.collect(Collectors.toSet());
}
/**
* Converting {@link Node} to {@link NodeGraph}
* @param node model object.
* @return graph node DTO or null
*/
public static NodeGraph nodeToNodeGraph(final Node node) {
if (node != null) {
return new NodeGraph(
node.getId(),
node.getName(),
node.getCounter()
);
} else {
return null;
}
}
/**
* Converting {@link NodeGraph} to {@link Node}
* @param nodeGraph graph node DTO.
* @return model object or null
*/
public static Node nodeGraphToNode(final NodeGraph nodeGraph) {
if (nodeGraph != null) {
return new Node(
nodeGraph.getId(),
nodeGraph.getName(),
nodeGraph.getCounter()
);
} else {
return null;
}
}
/**
* Converting collection of {@link Edge} to collection of {@link EdgeGraph}
* @param edges model objects collection.
* @return collection of graph edge DTO or empty collection if param is null.
*/
public static Set<EdgeGraph> edgesToEdgeGraphs(final Collection<Edge> edges) {
if (Objects.isNull(edges)) {
LOG.debug("edgesToEdgeGraphs: return empty collection because input collection of edges is null");
return new HashSet<>();
}
return edges.stream()
.filter(Objects::nonNull)
.map(GraphUtil::edgeToEdgeGraph)
.collect(Collectors.toSet());
}
/**
* Converting {@link Edge} to {@link EdgeGraph}
* @param edge model object.
* @return graph edge DTO or null
*/
public static EdgeGraph edgeToEdgeGraph(final Edge edge) {
if (edge != null) {
return new EdgeGraph(edge.getId(), edge.getNodeOne().getName(),
edge.getNodeTwo().getName());
} else {
return null;
}
}
/**
* Getting graph edges {@link Edge} from graph data transfer object {@link GraphDto}
* @param graph graph data transfer object
* @return collection of graph edges.
*/
public static Set<Edge> getEdgesFromGraphDto(final GraphDto graph) {
return graph.getEdges().stream()
.map(eg -> {
Node nodeOne = nodeGraphToNode(graph.getNodes().stream()
.filter(ng -> ng.getName().equalsIgnoreCase(eg.getNodeOne()))
.findFirst()
.orElse(null));
Node nodeTwo = nodeGraphToNode(graph.getNodes().stream()
.filter(ng -> ng.getName().equalsIgnoreCase(eg.getNodeTwo()))
.findFirst()
.orElse(null));
if (Objects.nonNull(nodeOne) && Objects.nonNull(nodeTwo)) {
return new Edge(eg.getId(), nodeOne, nodeTwo);
} else {
return null;
} })
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
/**
* Node random failure generator.
* @param nodes node graph collection
* @return map with key as node graph name and value as fault of node graph
* state or null if node graph collection is empty or null.
*/
public static Map<String, Boolean> getRandomNodeFault(Collection<NodeGraph> nodes) {
LOG.debug("getRandomNodeFault: Generating random node fault");
Map<String, Boolean> result = new HashMap<>();
if (Objects.isNull(nodes) || nodes.isEmpty()) {
return result;
}
nodes.forEach(nodeGraph -> {
boolean isFault = ThreadLocalRandom.current().nextInt(100) >= 90;
result.put(nodeGraph.getName(), isFault);
});
LOG.debug("getRandomNodeFault: result = " + result.toString());
return result;
}
}

View File

@@ -0,0 +1,63 @@
package ru.resprojects.linkchecker.util;
import org.slf4j.Logger;
import ru.resprojects.linkchecker.util.exeptions.ErrorPlaceType;
import ru.resprojects.linkchecker.util.exeptions.ErrorType;
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
import javax.servlet.http.HttpServletRequest;
/**
* Helper class for work with validation.
*/
public final class ValidationUtil {
public static final int NODE_COUNTER_DEFAULT = 0;
public static final int MIN_NAME_SIZE = 1;
public static final int MAX_NAME_SIZE = 20;
public static final String VALIDATOR_NOT_NULL_MESSAGE = "Object is not be null";
public static final String VALIDATOR_NODE_NOT_BLANK_NAME_MESSAGE = "Node name is not be empty";
public static final String VALIDATOR_NODE_NAME_RANGE_MESSAGE = "Node name must be at range from "
+ MIN_NAME_SIZE + " to " + MAX_NAME_SIZE;
private ValidationUtil() {
}
public static <T> T checkNotFound(T object, String msg, ErrorPlaceType place) {
checkNotFound(object != null, msg, place);
return object;
}
public static void checkNotFound(boolean found, String arg, ErrorPlaceType place) {
if (!found) {
throw new NotFoundException(arg, place);
}
}
// http://stackoverflow.com/a/28565320/548473
private static Throwable getRootCause(Throwable t) {
Throwable result = t;
Throwable cause;
while (null != (cause = result.getCause()) && (result != cause)) {
result = cause;
}
return result;
}
public static String getMessage(Throwable e) {
return e.getLocalizedMessage() != null ? e.getLocalizedMessage() : e.getClass().getName();
}
public static Throwable logAndGetRootCause(Logger log, HttpServletRequest req,
Exception e, boolean logException, ErrorType errorType) {
Throwable rootCause = ValidationUtil.getRootCause(e);
if (logException) {
log.error(errorType + " at request " + req.getRequestURL(), rootCause);
} else {
log.warn("{} at request {}: {}", errorType, req.getRequestURL(), rootCause.toString());
}
return rootCause;
}
}

View File

@@ -0,0 +1,45 @@
package ru.resprojects.linkchecker.util.exeptions;
import org.springframework.http.HttpStatus;
import java.util.Arrays;
public class ApplicationException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = -6458332116628903136L;
private final ErrorType type;
private final ErrorPlaceType place;
private final HttpStatus httpStatus;
private final String[] messages;
public ApplicationException(String messages, HttpStatus httpStatus) {
this(ErrorType.APP_ERROR, ErrorPlaceType.GRAPH, httpStatus, messages);
}
public ApplicationException(ErrorType type, ErrorPlaceType place, HttpStatus httpStatus, String... messages) {
super(String.format("type=%s, place=%s, msg=%s", type, place, Arrays.toString(messages)));
this.type = type;
this.place = place;
this.messages = messages;
this.httpStatus = httpStatus;
}
public ErrorType getType() {
return type;
}
public ErrorPlaceType getPlace() {
return place;
}
public HttpStatus getHttpStatus() {
return httpStatus;
}
public String[] getMessages() {
return messages;
}
}

View File

@@ -0,0 +1,74 @@
package ru.resprojects.linkchecker.util.exeptions;
import java.util.Arrays;
import java.util.StringJoiner;
/**
* Data class for exception handler.
*/
public class ErrorInfo {
private String url;
private ErrorType type;
private ErrorPlaceType place;
private String[] messages;
public ErrorInfo() {
}
/**
* Ctor.
* @param url REST request where an error has occurred
* @param type of error
* @param place where an error has occurred
* @param messages program error messages
*/
public ErrorInfo(String url, ErrorType type, ErrorPlaceType place, String... messages) {
this.url = url;
this.type = type;
this.place = place;
this.messages = messages;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public ErrorType getType() {
return type;
}
public void setType(ErrorType type) {
this.type = type;
}
public ErrorPlaceType getPlace() {
return place;
}
public void setPlace(ErrorPlaceType place) {
this.place = place;
}
public String[] getMessages() {
return messages;
}
public void setMessages(String[] messages) {
this.messages = messages;
}
@Override
public String toString() {
return new StringJoiner(", ", ErrorInfo.class.getSimpleName() + "[", "]")
.add("url='" + url + "'")
.add("type=" + type)
.add("place=" + place)
.add("messages=" + Arrays.toString(messages))
.toString();
}
}

View File

@@ -0,0 +1,12 @@
package ru.resprojects.linkchecker.util.exeptions;
/**
* Places where errors are an occurred
* EDGE - errors in edges of the graph.
* NODE - error in nodes of the graph.
* GRAPH - error in graph.
* APP - error in application.
*/
public enum ErrorPlaceType {
EDGE, NODE, GRAPH, APP
}

View File

@@ -0,0 +1,12 @@
package ru.resprojects.linkchecker.util.exeptions;
/**
* Types of errors what my be occurred in program
*/
public enum ErrorType {
APP_ERROR, //Types of errors that are associated with errors in the program as a whole
DATA_NOT_FOUND, //Types of errors that are associated with search and extract data
DATA_ERROR, //Types of errors that are associated with data handling
VALIDATION_ERROR, //Types of errors are associated with validating data in REST-controllers
WRONG_REQUEST //Types of errors that are associated with http requests
}

View File

@@ -0,0 +1,16 @@
package ru.resprojects.linkchecker.util.exeptions;
import org.springframework.http.HttpStatus;
public class NotFoundException extends ApplicationException {
/**
*
*/
private static final long serialVersionUID = 7379365858788450248L;
public NotFoundException(final String message, ErrorPlaceType place) {
super(ErrorType.DATA_NOT_FOUND, place, HttpStatus.UNPROCESSABLE_ENTITY, message);
}
}

View File

@@ -0,0 +1,61 @@
package ru.resprojects.linkchecker.web;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import ru.resprojects.linkchecker.util.exeptions.ErrorInfo;
import ru.resprojects.linkchecker.util.exeptions.ErrorPlaceType;
import ru.resprojects.linkchecker.util.exeptions.ErrorType;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.Map;
@Controller
public class CustomErrorController extends AbstractErrorController {
private static final Logger LOG = LoggerFactory.getLogger(CustomErrorController.class);
public CustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
@RequestMapping(value = "/error", produces = MediaType.TEXT_HTML_VALUE)
@ResponseBody
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
response.setStatus(status.value());
Map<String, Object> model = getModel(request);
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
LOG.debug("errorHtml: " + model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping(value = "/error", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ErrorInfo error(HttpServletRequest request) {
String errorRequestUrl = request.getScheme() + "://"+ request.getServerName()
+ ":" + request.getServerPort() + getModel(request).get("path").toString();
ErrorInfo errorInfo = new ErrorInfo(errorRequestUrl,
ErrorType.WRONG_REQUEST, ErrorPlaceType.APP, "Bad request");
LOG.debug("error: " + errorInfo);
return errorInfo;
}
private Map<String, Object> getModel(HttpServletRequest request) {
return Collections.unmodifiableMap(getErrorAttributes(request, true));
}
@Override
public String getErrorPath() {
return "/error";
}
}

View File

@@ -0,0 +1,15 @@
package ru.resprojects.linkchecker.web;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class EntryPointController {
@GetMapping("/")
public String index(Model model) {
return "index";
}
}

View File

@@ -0,0 +1,89 @@
package ru.resprojects.linkchecker.web.rest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import ru.resprojects.linkchecker.util.ValidationUtil;
import ru.resprojects.linkchecker.util.exeptions.ApplicationException;
import ru.resprojects.linkchecker.util.exeptions.ErrorInfo;
import ru.resprojects.linkchecker.util.exeptions.ErrorPlaceType;
import ru.resprojects.linkchecker.util.exeptions.ErrorType;
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
/**
* Exception handler for REST-controllers
*/
@RestControllerAdvice
public class ExceptionInfoHandler {
private static final Logger LOG = LoggerFactory.getLogger(ExceptionInfoHandler.class);
private final MessageSource messageSource;
@Autowired
public ExceptionInfoHandler(MessageSource messageSource) {
this.messageSource = messageSource;
}
@ExceptionHandler(value = {ApplicationException.class, NotFoundException.class})
public ResponseEntity<ErrorInfo> appError(HttpServletRequest req, ApplicationException appEx) {
return ResponseEntity.status(appEx.getHttpStatus())
.body(logAndGetErrorInfo(req,appEx, false,
appEx.getType(), appEx.getPlace(), appEx.getMessages()));
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public ErrorInfo handleError(HttpServletRequest req, Exception e) {
return logAndGetErrorInfo(req,e, true,
ErrorType.APP_ERROR, ErrorPlaceType.APP);
}
@ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY)
@ExceptionHandler({BindException.class, MethodArgumentNotValidException.class,
ConstraintViolationException.class})
public ErrorInfo validationsError(HttpServletRequest req, Exception e) {
String[] details;
if (e instanceof ConstraintViolationException) {
details = ((ConstraintViolationException) e).getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.toArray(String[]::new);
} else {
BindingResult result = e instanceof BindException ?
((BindException) e).getBindingResult() : ((MethodArgumentNotValidException) e).getBindingResult();
details = result.getFieldErrors().stream()
.map(this::getMessage)
.toArray(String[]::new);
}
return logAndGetErrorInfo(req, e, false,
ErrorType.VALIDATION_ERROR, ErrorPlaceType.APP, details);
}
private ErrorInfo logAndGetErrorInfo(HttpServletRequest req, Exception e,
boolean logException, ErrorType errorType, ErrorPlaceType placeType, String... msg) {
Throwable rootCause = ValidationUtil.logAndGetRootCause(LOG, req, e,
logException, errorType);
return new ErrorInfo(req.getRequestURL().toString(),errorType, placeType,
msg.length != 0 ? msg : new String[]{ValidationUtil.getMessage(rootCause)});
}
private String getMessage(MessageSourceResolvable resolvable) {
return messageSource.getMessage(resolvable, LocaleContextHolder.getLocale());
}
}

View File

@@ -0,0 +1,126 @@
package ru.resprojects.linkchecker.web.rest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import ru.resprojects.linkchecker.services.GraphService;
import javax.validation.Valid;
import java.net.URI;
import java.util.Collection;
import java.util.Set;
import static ru.resprojects.linkchecker.dto.GraphDto.EdgeGraph;
import static ru.resprojects.linkchecker.web.rest.GraphRestController.REST_URL;
/**
* REST controller for work with edges of the data graph
*/
@Validated
@RestController
@RequestMapping(value = GraphEdgeRestController.EDGE_REST_URL,
produces = MediaType.APPLICATION_JSON_VALUE)
public class GraphEdgeRestController {
private static final Logger LOG = LoggerFactory.getLogger(GraphEdgeRestController.class);
public static final String EDGE_REST_URL = REST_URL + "/edges";
private GraphService graphService;
@Autowired
public GraphEdgeRestController(final GraphService graphService) {
this.graphService = graphService;
}
@PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<EdgeGraph> create(@RequestBody @Valid EdgeGraph edge) {
LOG.info("Creating new graph edge");
EdgeGraph created = this.graphService.getEdges().create(edge);
URI uri = MvcUriComponentsBuilder.fromController(getClass())
.path(EDGE_REST_URL)
.buildAndExpand()
.toUri();
return ResponseEntity.created(uri).body(created);
}
//How to validate data in collections https://stackoverflow.com/a/54394177
@PostMapping(value = "create/byBatch", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Collection<EdgeGraph>> createByBatch(@RequestBody @Valid Set<EdgeGraph> edges) {
LOG.info("Creating new graph edges");
Set<EdgeGraph> created = this.graphService.getEdges().create(edges);
URI uri = MvcUriComponentsBuilder.fromController(getClass())
.path(EDGE_REST_URL)
.buildAndExpand()
.toUri();
return ResponseEntity.created(uri).body(created);
}
@GetMapping
public ResponseEntity<Collection<EdgeGraph>> get() {
LOG.info("Getting all graph edges");
return ResponseEntity.ok(this.graphService.getEdges().getAll());
}
@GetMapping(value = "/byId/{id}")
public ResponseEntity<EdgeGraph> getById(@PathVariable Integer id) {
LOG.info("Getting graph edge by id = " + id);
return ResponseEntity.ok(graphService.getEdges().getById(id));
}
@GetMapping(value = "/byName/{name}")
public ResponseEntity<Collection<EdgeGraph>> getByName(@PathVariable String name) {
LOG.info("Getting graph edge by name = " + name);
return ResponseEntity.ok(graphService.getEdges().get(name));
}
@GetMapping(value = "/byName")
@ResponseBody
public ResponseEntity<EdgeGraph> getByNames(@RequestParam("nodeOne") String nameNodeOne, @RequestParam("nodeTwo") String nameNodeTwo) {
LOG.info(String.format("Getting graph edge by node one {%s} and node two {%s} names", nameNodeOne, nameNodeTwo));
return ResponseEntity.ok(graphService.getEdges().get(nameNodeOne, nameNodeTwo));
}
@DeleteMapping
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void delete() {
LOG.info("Removing all graph edges");
graphService.getEdges().deleteAll();
}
@DeleteMapping(value = "/byId/{id}")
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void deleteById(@PathVariable Integer id) {
LOG.info("Removing graph edge by id = " + id);
graphService.getEdges().delete(id);
}
@DeleteMapping(value = "/byName/{name}")
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void deleteByName(@PathVariable String name) {
LOG.info("Removing graph edge by name = " + name);
graphService.getEdges().delete(name);
}
@DeleteMapping(value = "/byName")
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void deleteByNames(@RequestParam("nodeOne") String nodeOne, @RequestParam("nodeTwo") String nodeTwo) {
LOG.info(String.format("Removing graph edge by node one {%s} and node two {%s} names", nodeOne, nodeTwo));
graphService.getEdges().delete(nodeOne, nodeTwo);
}
}

View File

@@ -0,0 +1,117 @@
package ru.resprojects.linkchecker.web.rest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import ru.resprojects.linkchecker.services.GraphService;
import javax.validation.Valid;
import java.net.URI;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import static ru.resprojects.linkchecker.dto.GraphDto.NodeGraph;
import static ru.resprojects.linkchecker.web.rest.GraphRestController.REST_URL;
/**
* REST controller for work with nodes of the data graph
*/
@Validated
@RestController
@RequestMapping(value = GraphNodeRestController.NODES_REST_URL,
produces = MediaType.APPLICATION_JSON_VALUE)
public class GraphNodeRestController {
private static final Logger LOG = LoggerFactory.getLogger(GraphNodeRestController.class);
public static final String NODES_REST_URL = REST_URL + "/nodes";
private GraphService graphService;
@Autowired
public GraphNodeRestController(final GraphService graphService) {
this.graphService = graphService;
}
@PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<NodeGraph> create(@Valid @RequestBody NodeGraph node) {
LOG.info("Creating new graph node");
NodeGraph created = this.graphService.getNodes().create(node);
URI uri = MvcUriComponentsBuilder.fromController(getClass())
.path(NODES_REST_URL)
.buildAndExpand()
.toUri();
return ResponseEntity.created(uri).body(created);
}
@PostMapping(value = "/create/byBatch", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Collection<NodeGraph>> createByBatch(@RequestBody @Valid Set<NodeGraph> nodes) {
LOG.info("Creating new graph nodes");
Set<NodeGraph> created = this.graphService.getNodes().create(nodes);
URI uri = MvcUriComponentsBuilder.fromController(getClass())
.path(NODES_REST_URL)
.buildAndExpand()
.toUri();
return ResponseEntity.created(uri).body(created);
}
@GetMapping
public ResponseEntity<Collection<NodeGraph>> get() {
LOG.info("Getting all graph nodes");
return ResponseEntity.ok(this.graphService.getNodes().getAll());
}
@GetMapping(value = "/byId/{id}")
public ResponseEntity<NodeGraph> getById(@PathVariable Integer id) {
LOG.info("Getting graph node by id = " + id);
return ResponseEntity.ok(graphService.getNodes().getById(id));
}
@GetMapping(value = "/byName/{name}")
public ResponseEntity<NodeGraph> getByName(@PathVariable String name) {
LOG.info("Getting graph node by name = " + name);
return ResponseEntity.ok(graphService.getNodes().get(name));
}
@DeleteMapping(value = "/byId/{id}")
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void deleteById(@PathVariable Integer id) {
LOG.info("Removing graph node by id = " + id);
graphService.getNodes().delete(id);
}
@DeleteMapping(value = "/byName/{name}")
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void deleteByName(@PathVariable String name) {
LOG.info("Removing graph node by name = " + name);
graphService.getNodes().delete(name);
}
@DeleteMapping(value = "/byObj")
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void deleteByObject(@RequestBody NodeGraph obj) {
LOG.info("Removing graph node by object = " + Optional.ofNullable(obj));
graphService.getNodes().delete(obj);
}
@DeleteMapping
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void delete() {
LOG.info("Removing all graph nodes and graph edges that are linked with these nodes");
graphService.getNodes().deleteAll();
}
}

View File

@@ -0,0 +1,90 @@
package ru.resprojects.linkchecker.web.rest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import ru.resprojects.linkchecker.dto.GraphDto;
import ru.resprojects.linkchecker.services.GraphService;
import javax.validation.Valid;
import java.net.URI;
import java.util.Set;
/**
* REST controller for work with data graph in generally
*/
@Validated
@RestController
@RequestMapping(value = GraphRestController.REST_URL, produces = MediaType.APPLICATION_JSON_VALUE)
public class GraphRestController {
private static final Logger LOG = LoggerFactory.getLogger(GraphRestController.class);
public static final String REST_URL = "/rest/v1/graph";
private GraphService graphService;
@Autowired
public GraphRestController(final GraphService graphService) {
this.graphService = graphService;
}
@RequestMapping(method = RequestMethod.OPTIONS)
public ResponseEntity<?> options() {
return ResponseEntity
.ok()
.allow(HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE, HttpMethod.OPTIONS)
.build();
}
@GetMapping
public ResponseEntity<GraphDto> get() {
LOG.info("Getting graph");
return ResponseEntity.ok(this.graphService.get());
}
@GetMapping(value = "/export", produces = MediaType.TEXT_HTML_VALUE)
public ResponseEntity<String> exportToGraphViz() {
LOG.info("Export graph to GraphViz format");
return ResponseEntity.ok(this.graphService.exportToGraphViz());
}
@PostMapping(value = "/checkroute", produces = MediaType.TEXT_HTML_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> checkRoute(@RequestBody Set<String> nodeNames) {
LOG.info("Checking route for nodes " + nodeNames.toString());
return ResponseEntity.ok(this.graphService.checkRoute(nodeNames));
}
@PostMapping(value = "/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<GraphDto> create(@RequestBody @Valid GraphDto graph) {
LOG.info("Creating new graph");
GraphDto created = graphService.create(graph);
URI uri = MvcUriComponentsBuilder.fromController(getClass())
.path(REST_URL)
.buildAndExpand()
.toUri();
return ResponseEntity.created(uri).body(created);
}
@DeleteMapping
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public void delete() {
LOG.info("Removing graph");
graphService.clear();
}
}

View File

@@ -0,0 +1,109 @@
spring:
profiles:
active: demo
logging:
level:
ru.resprojects: info
---
spring:
profiles: production
jpa:
database: postgresql
generate-ddl: true
properties:
hibernate:
jdbc:
lob:
non_contextual_creation: true
database-platform: org.hibernate.dialect.PostgreSQL9Dialect
open-in-view: false
datasource:
url: ${LINKCHECKER_PGSQL_DB_HOST}:${LINKCHECKER_PGSQL_DB_PORT}/${LINKCHECKER_PGSQL_DB_NAME}
username: ${LINKCHECKER_PGSQL_DB_USER}
password: ${LINKCHECKER_PGSQL_DB_PASSWORD}
platform: postgresql
initialization-mode: never
---
spring:
profiles: demo, test
jpa:
database: h2
open-in-view: false
hibernate:
ddl-auto: none
datasource:
initialization-mode: always
platform: h2
---
spring:
profiles: test
datasource:
url: jdbc:h2:mem:linkchecker;DB_CLOSE_ON_EXIT=FALSE
thymeleaf:
cache: false
---
spring:
profiles: demo
datasource:
url: jdbc:h2:mem:linkchecker;DB_CLOSE_ON_EXIT=TRUE
---
spring:
profiles: debug
jpa:
show-sql: true
properties:
hibernate:
generate_statistics: true
logging:
level:
ru.resprojects: debug
---
spring:
profiles: moc_test
---
spring:
profiles: demo, production, test
jpa:
properties:
hibernate:
jdbc:
batch_size: 10
order_inserts: true
order_updates: true
---
spring:
http:
converters:
preferred-json-mapper: gson
output:
ansi:
enabled: detect
logging:
level:
org.springframework: error
pattern:
file: "%d %p %c{1.} [%t] %m%n"
console: "%clr(%d{HH:mm:ss.SSS}){yellow} %clr(%-5p) %clr(---){faint} %clr([%t]){cyan} %clr(%logger{36}){blue} %clr(:){red} %clr(%msg){faint}%n"
file:
name: /log/linkchecker.log
max-size: 5MB
appmsg:
app-msg:
MSG_ARGUMENT_NULL: "Argument must not be null"
MSG_COLLECTION_EMPTY: "Collection must not be empty"
MSG_COLLECTION_CONTAIN_NULL: "Collection must not contain a null item"
MSG_COLLECTION_CONTAIN_ONE_ELEMENT: "Collection must have more than one element"
MSG_BY_ID_ERROR: "%s with ID = %d is not found"
edge-msg:
EDGE_MSG_GET_ERROR: "Edge for nodes [%s, %s] is not found"
EDGE_MSG_ALREADY_PRESENT_ERROR: "Edge for nodes ([%s, %s], [%s, %s]) already present in the graph"
EDGE_MSG_GET_BY_NAME_ERROR: "Edges for node %s is not found"
node-msg:
NODE_MSG_ALREADY_PRESENT_ERROR: "Node %s already present in the graph"
NODE_MSG_UPDATE_ERROR: "Error while update node with id = "
NODE_MSG_BY_NAME_ERROR: "Node with NAME = %s is not found"
NODE_MSG_BY_OBJECT_ERROR: "Node %s is not found"
NODE_MSG_NOT_REACHABLE: "Nodes %s and %s are not reachable to each other"
NODE_MSG_IS_FAULT: "Node %s is fault"

View File

@@ -0,0 +1,17 @@
DELETE FROM edges;
DELETE FROM nodes;
ALTER SEQUENCE global_seq RESTART WITH 5000;
INSERT INTO nodes (name) VALUES
('v1'),
('v2'),
('v3'),
('v4'),
('v5');
INSERT INTO edges (nodeOne, nodeTwo) VALUES
(5000, 5001),
(5000, 5002),
(5000, 5004),
(5002, 5003);

View File

@@ -0,0 +1,17 @@
DELETE FROM edges;
DELETE FROM nodes;
ALTER SEQUENCE global_seq RESTART WITH 5000;
INSERT INTO nodes (name) VALUES
('v1'),
('v2'),
('v3'),
('v4'),
('v5');
INSERT INTO edges (nodeOne, nodeTwo) VALUES
(5000, 5001),
(5000, 5002),
(5000, 5004),
(5002, 5003);

View File

@@ -0,0 +1,22 @@
DROP TABLE IF EXISTS nodes CASCADE;
DROP TABLE IF EXISTS edges;
DROP SEQUENCE IF EXISTS global_seq;
CREATE SEQUENCE global_seq MINVALUE 5000;
CREATE TABLE nodes (
id INT DEFAULT global_seq.nextval PRIMARY KEY,
name VARCHAR NOT NULL,
counter INT DEFAULT 0 NOT NULL
);
CREATE UNIQUE INDEX nodes_unique_name_idx ON nodes(name);
CREATE TABLE edges (
id INT DEFAULT global_seq.nextval PRIMARY KEY,
nodeone INT NOT NULL,
nodetwo INT NOT NULL,
FOREIGN KEY (nodeone) REFERENCES nodes(id) ON DELETE CASCADE,
FOREIGN KEY (nodetwo) REFERENCES nodes(id) ON DELETE CASCADE,
CHECK (nodeone <> nodetwo),
CONSTRAINT unique_edge UNIQUE (nodeone, nodetwo)
);

View File

@@ -0,0 +1,22 @@
DROP TABLE IF EXISTS nodes CASCADE;
DROP TABLE IF EXISTS edges;
DROP SEQUENCE IF EXISTS global_seq CASCADE;
CREATE SEQUENCE global_seq START 5000;
CREATE TABLE nodes (
id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'),
name VARCHAR NOT NULL,
counter INTEGER DEFAULT 0 NOT NULL
);
CREATE UNIQUE INDEX nodes_unique_name_idx ON nodes(name);
CREATE TABLE edges (
id INTEGER PRIMARY KEY DEFAULT nextval('global_seq'),
nodeone INT NOT NULL,
nodetwo INT NOT NULL,
FOREIGN KEY (nodeone) REFERENCES nodes(id) ON DELETE CASCADE,
FOREIGN KEY (nodetwo) REFERENCES nodes(id) ON DELETE CASCADE,
CHECK (nodeone <> nodetwo),
CONSTRAINT unique_edge UNIQUE (nodeone, nodetwo)
);

View File

@@ -0,0 +1,16 @@
.starter-template {
padding: 3rem 1.5rem;
text-align: center;
}
.p-0 {
padding: 0%
}
h1 {
color:#0000FF;
}
h2 {
color:#FF0000;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,34 @@
<!DOCTYPE HTML>
<html lang="ru" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Linkchecker (тестовое задание)</title>
<link rel="stylesheet" th:href="@{/webjars/bootstrap/4.3.1/css/bootstrap.min.css}"/>
<link rel="stylesheet" th:href="@{/css/main.css}"/>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top">
<a href="/" class="navbar-brand">
<img src="/pics/logo.png" width="52" height="52" alt="logo">
RESPROJECTS.RU
</a>
</nav>
<div class="container-fluid starter-template">
<div class="container">
<div class="row text-center justify-content-center">
<img src="/pics/404.jpg" alt="">
</div>
<a href="/">Go Back</a>
</div>
</div>
<script type="text/javascript" th:src="@{/webjars/bootstrap/4.3.1/js/bootstrap.min.js}"></script>
</body>
</html>

View File

@@ -0,0 +1,82 @@
<!DOCTYPE HTML>
<html lang="ru" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Linkchecker (тестовое задание)</title>
<link rel="stylesheet" th:href="@{webjars/bootstrap/4.3.1/css/bootstrap.min.css}"/>
<link rel="stylesheet" th:href="@{/css/main.css}"/>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top">
<a href="/" class="navbar-brand">
<img src="pics/logo.png" width="52" height="52" alt="logo">
RESPROJECTS.RU
</a>
<div class="navbar-nav mr-auto">
</div>
<a class="btn btn-success my2 my-sm-0" href="/docs/api-guide-v1.html" role="button">Linkchecker REST API Guide v1</a>
</nav>
<div class="container-fluid p-0">
<div class="container">
<h1>Linkchecker (тестовое задание)</h1>
<h2>Оригинальный текст задания</h2>
<p>Разработать REST-сервис, проверяющий работоспособность любой последовательности узлов.
Каждый узел имеет уникальное имя, вероятность, с которой откажет при обращении к нему, и счетчик успешно выполненных запросов.</p>
<p>Сервис должен реализовывать два POST-метода:</p>
<ol>
<li><b>setNodes</b> - устанавливает граф из узлов, описанных выше. Формат входных данных - JSON. Программа должна исключать циклические связи узлов.</li>
<li><b>checkRoute</b> - принимает набор вершин (или их идентификаторов) в формате JSON и проходит по этим вершинам, проверяя на каждом пройденном узле, не отказал ли он. Если путь существует в графе и ни один из узлов пути не отказал, следует увеличить счетчик в каждом из узлов пути. В противном случае отображать ошибку в ответе POST-метода (произвольный формат).</li>
<li>Узлы и связи должны храниться в базе данных.</li>
</ol>
<h2>Изменённый вариант задания</h2>
<p>После введённых корректировок, итоговый вид тестового задания выглядит следующим образом:</p>
<p>Разработать REST-сервис, проверяющий работоспособность любой последовательности узлов.</p>
<ol>
<li>Каждый узел имеет уникальное имя и счетчик успешно выполненных запросов, так же для хранения в базе данных есть уникальный идентификатор, который присваивается автоматически при записи в БД. Был исключен элемент вероятность отказа узла. Этот параметр перестал быть нужным, так как вероятность отказа узла стала случайным фактором, возникающая автоматически, во время проверки последовательности узлов.</li>
<li>Граф в программе неориентированный, т.е. вершины графа связаны друг с другом рёбрами, не имеющими направления. В базе данных, хранение графа осуществляется в двух таблицах. В одной таблице осуществляется хранение набора узлов графа, в другой набор рёбер графа. Более подробно смотрите запись в wiki <a href="https://gitlab.com/Aleksandrov/linkchecker/wikis/%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85">План работы</a></li>
<li>Программа позволяет делать следующее:
<ol>
<li>Работать с графом обобщённо:
<ol>
<li>Создавать новый граф (при этом информация о прежнем графе будет удалена с БД)</li>
<li>Извлекать информацию о графе в заданном формате</li>
<li>Удалять целиком весь граф</li>
<li>Проверять работоспособность заданной последовательности узлов (по условию задачи) выполнив соответствующий запрос.</li>
</ol>
</li>
<li>Работать с узлами и ребрами по отдельности, т.е. добавлять, удалять, искать информацию по заданным параметрам. Ручное изменение какой-либо информации о узле и ребре не предусмотрена, т.е. возможно либо добавления узла или ребра в БД или удаление из БД)</li>
</ol>
</li>
</ol>
<p>Так как по условию задания, граф должен исключить все виды циклов (т.е. граф должен быть ациклическим), то при любом запросе информации о графе целиком или же при проверки набора заданных узлов будет происходить автоматический поиск и удаление циклов из графа. Удаление циклов происходит при помощи удаления набора рёбер, создающие циклы. Поэтому в случае обнаружения циклов в графе, исходный набор рёбер будет отличаться от набора рёбер хранящихся в БД.</p>
<p>Автоматический поиск и удаление циклов не срабатывает, если происходит работа только с набором данных рёбер графа в отдельности, т.е. можно добавлять в базу рёбра, образующие циклы.</p>
<p><b>Используемый стек</b> : <b>Spring Boot</b>, <b>Spring Data</b>, <b>ORM (Hibernate)</b>, <a href="https://jgrapht.org/"><b>JGraphT</b></a> : (для работы с графом), <b>GSON</b> (используется вместо используемого по умолчанию Jackson для работы с json),
<b>Thymeleaf</b> и <b>Bootstrap</b> (используется для формирования стартовой информационной страницы), <b>Mockito</b> (идёт вместе с Spring Boot),
<b>Powermock</b> (подключается отдельной библиотекой, используется в дополнении к mockito для тестов)</p>
<p><b>Хранилище данных</b> : PostgeSQL (для production), H2 (для тестов)</p>
</div>
</div>
<script type="text/javascript" th:src="@{webjars/bootstrap/4.3.1/js/bootstrap.min.js}"></script>
</body>
</html>

View File

@@ -0,0 +1,48 @@
package ru.resprojects.linkchecker;
import com.google.gson.Gson;
import ru.resprojects.linkchecker.dto.GraphDto;
import java.lang.reflect.Type;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Helper class for unit testing.
*/
public class TestUtils {
public static final GraphDto.NodeGraph nodeGraph = new GraphDto.NodeGraph(5000, "v1", 0);
public static final GraphDto.EdgeGraph edgeGraph = new GraphDto.EdgeGraph(5005, "v1", "v2");
public static final Set<GraphDto.NodeGraph> nodesGraph = Stream.of(
nodeGraph,
new GraphDto.NodeGraph(5001, "v2", 0),
new GraphDto.NodeGraph(5002, "v3", 0),
new GraphDto.NodeGraph(5003, "v4", 0),
new GraphDto.NodeGraph(5004, "v5", 0)
).collect(Collectors.toSet());
public static final Set<GraphDto.EdgeGraph> edgesGraph = Stream.of(
edgeGraph,
new GraphDto.EdgeGraph(5006, "v1", "v3"),
new GraphDto.EdgeGraph(5007, "v1", "v5"),
new GraphDto.EdgeGraph(5008, "v3", "v4")
).collect(Collectors.toSet());
public static final GraphDto graph = new GraphDto(nodesGraph, edgesGraph);
private TestUtils() {
}
public static String mapToJson(Object obj) {
return new Gson().toJson(obj);
}
public static <T> T mapFromJson(String json, Type type) {
return new Gson().fromJson(json, type);
}
public static <T> T mapFromJson(String json, Class<T> clazz) {
return new Gson().fromJson(json, clazz);
}
}

View File

@@ -0,0 +1,86 @@
package ru.resprojects.linkchecker.model;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import ru.resprojects.linkchecker.LinkcheckerApplication;
import ru.resprojects.linkchecker.util.ValidationUtil;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LinkcheckerApplication.class)
@ActiveProfiles(profiles = "moc_test")
public class ValidationModelTests {
private static final Logger LOG = LoggerFactory.getLogger(ValidationModelTests.class);
private static ValidatorFactory validatorFactory;
private static Validator validator;
@BeforeClass
public static void createValidator() {
validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.getValidator();
}
@AfterClass
public static void close() {
validatorFactory.close();
}
@Test
public void tryCreateNodeWithBlankName() {
Node node = new Node("");
Set<ConstraintViolation<Node>> violations = validator.validate(node);
Assert.assertEquals(2, violations.size());
ConstraintViolation<Node> violation = violations.stream()
.filter(v -> ValidationUtil.VALIDATOR_NODE_NOT_BLANK_NAME_MESSAGE.equals(v.getMessage()))
.findFirst()
.orElse(null);
Assert.assertNotNull(violation);
violation = violations.stream()
.filter(v -> ValidationUtil.VALIDATOR_NODE_NAME_RANGE_MESSAGE.equals(v.getMessage()))
.findFirst()
.orElse(null);
Assert.assertNotNull(violation);
printViolationMessage(violations);
}
@Test
public void tryCreateNodeWithTooLongNodeName() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 51; i++) {
sb.append('a');
}
Node node = new Node(sb.toString());
Set<ConstraintViolation<Node>> violations = validator.validate(node);
Assert.assertEquals(1, violations.size());
ConstraintViolation<Node> violation = violations.stream()
.filter(v -> ValidationUtil.VALIDATOR_NODE_NAME_RANGE_MESSAGE.equals(v.getMessage()))
.findFirst()
.orElse(null);
Assert.assertNotNull(violation);
printViolationMessage(violations);
}
private static void printViolationMessage(Set<ConstraintViolation<Node>> violations) {
violations.forEach(v -> {
LOG.info("VIOLATION INFO");
LOG.info("--> MESSAGE: " + v.getMessage());
LOG.info("--> PROPERTY PATH: " + v.getPropertyPath().toString());
LOG.info("--> INVALID VALUE: " + v.getInvalidValue());
});
}
}

View File

@@ -0,0 +1,144 @@
package ru.resprojects.linkchecker.repositories;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.junit4.SpringRunner;
import ru.resprojects.linkchecker.LinkcheckerApplication;
import ru.resprojects.linkchecker.model.Edge;
import ru.resprojects.linkchecker.model.Node;
import java.util.ArrayList;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LinkcheckerApplication.class)
@ActiveProfiles(profiles = {"test", "debug"})
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
config = @SqlConfig(encoding = "UTF-8"))
public class EdgeRepositoryH2DBTests {
private static final Logger LOG = LoggerFactory.getLogger(EdgeRepositoryH2DBTests.class);
@Autowired
EdgeRepository edgeRepository;
@Autowired
NodeRepository nodeRepository;
@Test
public void persistNewEdge() {
Node nodeOne = nodeRepository.save(new Node("v6"));
Node nodeTwo = nodeRepository.getByName("v5");
Edge edge = edgeRepository.save(new Edge(nodeOne, nodeTwo));
Assert.assertNotNull(edge);
LOG.info("New Edge: " + edge);
List<Edge> edges = edgeRepository.findAll();
Assert.assertNotNull(edges);
Assert.assertEquals(5, edges.size());
LOG.info("EDGES count = " + edges.size());
}
@Test
public void persistEdgeList() {
List<Node> nodes = nodeRepository.findAll();
Node nodeOne = nodeRepository.save(new Node("v6"));
List<Edge> edges = new ArrayList<>();
nodes.forEach(node -> edges.add(new Edge(nodeOne, node)));
edgeRepository.saveAll(edges);
List<Edge> newEdgeList = edgeRepository.findAll();
Assert.assertNotNull(newEdgeList);
Assert.assertEquals(9, newEdgeList.size());
LOG.info("Size: " + newEdgeList.size());
newEdgeList.forEach(edge -> LOG.info(edge.toString()));
}
@Test
public void getAllEdges() {
List<Edge> edges = edgeRepository.findAll();
Assert.assertNotNull(edges);
Assert.assertEquals(4, edges.size());
LOG.info("LIST count = " + edges.size());
edges.forEach(edge -> LOG.info(edge.toString()));
}
@Test
public void getEdgeById() {
Edge edge = edgeRepository.findById(5005).orElse(null);
Assert.assertNotNull(edge);
Assert.assertEquals("v1", edge.getNodeOne().getName());
LOG.info("EDGE INFO WITH ID = 5005: " + edge);
}
@Test
public void getEdgeByNodeOneAndNodeTwo() {
Node nodeOne = nodeRepository.getByName("v1");
Node nodeTwo = nodeRepository.getByName("v2");
Edge edge = edgeRepository.findEdgeByNodeOneAndNodeTwo(nodeOne, nodeTwo).orElse(null);
Assert.assertNotNull(edge);
Assert.assertEquals(new Integer(5005), edge.getId());
LOG.info("EDGE FOR NODES v1 and v2: " + edge);
}
@Test
public void getEdgeByNodeOneOrNodeTwo() {
Node nodeOne = nodeRepository.getByName("v1");
Node nodeTwo = nodeRepository.getByName("v2");
List<Edge> edges = edgeRepository.findEdgesByNodeOneOrNodeTwo(nodeOne, null);
Assert.assertNotNull(edges);
Assert.assertNotEquals(0, edges.size());
edges.forEach(edge -> LOG.info(edge.toString()));
LOG.info("-------------");
edges = edgeRepository.findEdgesByNodeOneOrNodeTwo(null, nodeTwo);
Assert.assertNotNull(edges);
Assert.assertNotEquals(0, edges.size());
edges.forEach(edge -> LOG.info(edge.toString()));
LOG.info("-------------");
edges = edgeRepository.findEdgesByNodeOneOrNodeTwo(nodeOne, nodeTwo);
Assert.assertNotNull(edges);
Assert.assertNotEquals(0, edges.size());
edges.forEach(edge -> LOG.info(edge.toString()));
LOG.info("-------------");
edges = edgeRepository.findEdgesByNodeOneOrNodeTwo(nodeTwo, nodeTwo);
Assert.assertNotNull(edges);
Assert.assertNotEquals(0, edges.size());
edges.forEach(edge -> LOG.info(edge.toString()));
}
@Test
public void deleteEdgesInBatch() {
Node node = nodeRepository.getByName("v1");
List<Edge> edges = edgeRepository.findEdgesByNodeOneOrNodeTwo(node, node);
Assert.assertNotNull(edges);
edgeRepository.deleteInBatch(edges);
edges = edgeRepository.findAll();
Assert.assertNotNull(edges);
edges.forEach(edge -> LOG.info(edge.toString()));
}
@Test
public void deleteById() {
edgeRepository.deleteById(5005);
List<Edge> edges = edgeRepository.findAll();
Assert.assertNotNull(edges);
Assert.assertEquals(3, edges.size());
LOG.info("EDGES count = " + edges.size());
}
@Test
public void deleteAllEdges() {
edgeRepository.deleteAllInBatch();
List<Edge> edges = edgeRepository.findAll();
Assert.assertNotNull(edges);
Assert.assertEquals(0, edges.size());
}
}

View File

@@ -0,0 +1,139 @@
package ru.resprojects.linkchecker.repositories;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.junit4.SpringRunner;
import ru.resprojects.linkchecker.LinkcheckerApplication;
import ru.resprojects.linkchecker.model.Edge;
import ru.resprojects.linkchecker.model.Node;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LinkcheckerApplication.class)
@ActiveProfiles(profiles = {"test", "debug"})
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
config = @SqlConfig(encoding = "UTF-8"))
public class NodeRepositoryH2DBTests {
private static final Logger LOG = LoggerFactory.getLogger(NodeRepositoryH2DBTests.class);
@Autowired
NodeRepository nodeRepository;
@Autowired
EdgeRepository edgeRepository;
@Test
public void persistNewNode() {
Node node = new Node("v11");
Node savedNode = nodeRepository.save(node);
Assert.assertNotNull(savedNode.getId());
LOG.info(nodeRepository.getByName("v11").toString());
int count = nodeRepository.findAll().size();
Assert.assertEquals(6, count);
LOG.info("LIST count = " + count);
}
@Test
public void persistNodeList() {
List<Node> nodes = new ArrayList<>();
IntStream.range(1, 60).forEach(i -> nodes.add(new Node("w" + i)));
nodeRepository.saveAll(nodes);
List<Node> savedNodes = nodeRepository.findAll();
Assert.assertNotNull(savedNodes);
Assert.assertEquals(64, savedNodes.size());
LOG.info("LIST count = " + savedNodes.size());
savedNodes.forEach(node -> LOG.info(node.toString()));
}
@Test
public void getAllNodes() {
List<Node> nodes = nodeRepository.findAll();
Assert.assertNotNull(nodes);
Assert.assertEquals(5, nodes.size());
LOG.info("LIST count = " + nodes.size());
nodes.forEach(node -> LOG.info(node.toString()));
}
@Test
public void getNodeById() {
Node node = nodeRepository.findById(5000).orElse(null);
Assert.assertNotNull(node);
Assert.assertEquals("v1", node.getName());
LOG.info("NODE INFO WITH ID = 5000: " + node);
}
@Test
public void nodeNotFoundById() {
Node node = nodeRepository.findById(5010).orElse(null);
Assert.assertNull(node);
}
@Test
public void getNodeByName() {
Node node = nodeRepository.getByName("v1");
Assert.assertNotNull(node);
Assert.assertEquals("v1", node.getName());
LOG.info("NODE INFO WITH ID = 5000: " + node);
}
@Test
public void nodeNotFoundIfNameIsNull() {
Node node = nodeRepository.getByName(null);
Assert.assertNull(node);
}
@Test
public void nodeNotFoundByName() {
Node node = nodeRepository.getByName("v11");
Assert.assertNull(node);
}
@Test
public void deleteById() {
nodeRepository.deleteById(5000);
List<Node> nodes = nodeRepository.findAll();
Assert.assertNotNull(nodes);
Assert.assertEquals(4, nodes.size());
LOG.info("LIST count = " + nodes.size());
nodes.forEach(node -> LOG.info(node.toString()));
}
@Test
public void deleteByName() {
nodeRepository.deleteByName("v1");
List<Node> nodes = nodeRepository.findAll();
Assert.assertNotNull(nodes);
Assert.assertEquals(4, nodes.size());
LOG.info("LIST count = " + nodes.size());
nodes.forEach(node -> LOG.info(node.toString()));
}
@Test
public void cascadeDeleteAllNodesAndEdges() {
nodeRepository.deleteAllInBatch();
List<Node> nodes = nodeRepository.findAll();
Assert.assertNotNull(nodes);
Assert.assertEquals(0, nodes.size());
List<Edge> edges = edgeRepository.findAll();
Assert.assertNotNull(edges);
Assert.assertEquals(0, edges.size());
}
@Test
public void existNodeByName() {
Assert.assertTrue(nodeRepository.existsByName("v1"));
}
}

View File

@@ -0,0 +1,259 @@
package ru.resprojects.linkchecker.services;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.junit4.SpringRunner;
import ru.resprojects.linkchecker.AppProperties;
import ru.resprojects.linkchecker.LinkcheckerApplication;
import ru.resprojects.linkchecker.TestUtils;
import ru.resprojects.linkchecker.util.exeptions.ApplicationException;
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static ru.resprojects.linkchecker.dto.GraphDto.EdgeGraph;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LinkcheckerApplication.class)
@ActiveProfiles(profiles = {"test", "debug"})
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
config = @SqlConfig(encoding = "UTF-8"))
public class GraphEdgeServiceH2DBTests {
private static final Logger LOG = LoggerFactory.getLogger(GraphEdgeServiceH2DBTests.class);
@Rule
public ExpectedException thrown = ExpectedException.none();
@Autowired
private GraphEdgeService edgeService;
@Autowired
private AppProperties properties;
@Test
public void createEdge() {
EdgeGraph edgeGraph = new EdgeGraph("v1", "v4");
EdgeGraph actual = edgeService.create(edgeGraph);
Assert.assertNotNull(actual);
Assert.assertEquals(new Integer(5009), actual.getId());
Set<EdgeGraph> egList = edgeService.getAll();
Assert.assertEquals(5, egList.size());
egList.forEach(eg -> LOG.info("---- EDGE: " + eg));
}
@Test
public void createEdgeNullArgumentException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_ARGUMENT_NULL"));
edgeService.create((EdgeGraph) null);
}
@Test
public void createEdgeNodeNotFoundException() {
EdgeGraph edgeGraph = new EdgeGraph("v10", "v4");
thrown.expect(NotFoundException.class);
thrown.expectMessage( String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), "v10"));
edgeService.create(edgeGraph);
}
@Test
public void createEdgeAlreadyPresentException() {
EdgeGraph edgeGraph = new EdgeGraph("v1", "v2");
thrown.expect(ApplicationException.class);
thrown.expectMessage(String.format(properties.getEdgeMsg().get("EDGE_MSG_ALREADY_PRESENT_ERROR"), "v1", "v2", "v2", "v1"));
edgeService.create(edgeGraph);
}
@Test
public void createEdgeAlreadyPresentVariantTwoException() {
EdgeGraph edgeGraph = new EdgeGraph("v2", "v1");
thrown.expect(ApplicationException.class);
thrown.expectMessage(String.format(properties.getEdgeMsg().get("EDGE_MSG_ALREADY_PRESENT_ERROR"), "v2", "v1", "v1", "v2"));
edgeService.create(edgeGraph);
}
@Test
public void createEdges() {
Set<EdgeGraph> edgeGraphs = Stream.of(
new EdgeGraph("v2", "v3"),
new EdgeGraph("v2", "v5"),
new EdgeGraph("v3", "v5")
).collect(Collectors.toSet());
Set<EdgeGraph> actual = edgeService.create(edgeGraphs);
Assert.assertFalse(actual.isEmpty());
Assert.assertNotNull(actual.iterator().next().getId());
actual.forEach(eg -> LOG.info("---- RETURNED EDGE: " + eg));
Set<EdgeGraph> egList = edgeService.getAll();
Assert.assertEquals(7, egList.size());
egList.forEach(eg -> LOG.info("---- EDGE: " + eg));
}
@Test
public void createEdgesEmptyCollectionException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_COLLECTION_EMPTY"));
edgeService.create(new HashSet<>());
}
@Test
public void createEdgesNodeNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), "v21"));
Set<EdgeGraph> edgeGraphs = Stream.of(
new EdgeGraph("v21", "v3"),
new EdgeGraph("v2", "v5"),
new EdgeGraph("v3", "v5")
).collect(Collectors.toSet());
edgeService.create(edgeGraphs);
}
@Test
public void createEdgesCollectionContainNullException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_COLLECTION_CONTAIN_NULL"));
Set<EdgeGraph> edgeGraphs = Stream.of(
null,
new EdgeGraph("v2", "v5"),
new EdgeGraph("v3", "v5")
).collect(Collectors.toSet());
edgeService.create(edgeGraphs);
}
@Test
public void createEdgesEdgeAlreadyPresentException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(String.format(properties.getEdgeMsg().get("EDGE_MSG_ALREADY_PRESENT_ERROR"), "v1", "v2", "v2", "v1"));
Set<EdgeGraph> edgeGraphs = Stream.of(
new EdgeGraph("v1", "v2"),
new EdgeGraph("v2", "v5")
).collect(Collectors.toSet());
edgeService.create(edgeGraphs);
}
@Test
public void deleteEdgeById() {
edgeService.delete(5005);
Set<EdgeGraph> egList = edgeService.getAll();
Assert.assertEquals(3, egList.size());
egList.forEach(eg -> LOG.info("---- EDGE: " + eg));
}
@Test
public void deleteEdgeByIdNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"), "EDGE", 5022));
edgeService.delete(5022);
}
@Test
public void deleteEdgeByNodeOneAndNodeTwoNames() {
edgeService.delete("v1", "v2");
Set<EdgeGraph> actual = edgeService.getAll();
actual.forEach(eg -> LOG.info("---- EDGE: " + eg));
}
@Test
public void deleteEdgeByNodeOneAndNodeTwoNamesNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getEdgeMsg().get("EDGE_MSG_GET_ERROR"), "v15", "v2"));
edgeService.delete("v15", "v2");
}
@Test
public void deleteEdgeByNodeName() {
edgeService.delete("v1");
Set<EdgeGraph> actual = edgeService.getAll();
actual.forEach(eg -> LOG.info("---- EDGE: " + eg));
}
@Test
public void deleteEdgeByNodeNameNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getEdgeMsg().get("EDGE_MSG_GET_BY_NAME_ERROR"), "v15"));
edgeService.delete("v15");
}
@Test
public void deleteAllEdges() {
edgeService.deleteAll();
Set<EdgeGraph> actual = edgeService.getAll();
Assert.assertTrue(actual.isEmpty());
}
@Test
public void getAllEdges() {
Set<EdgeGraph> actual = edgeService.getAll();
Assert.assertEquals(4, actual.size());
assertThat(actual.stream()
.filter(eg -> eg.getId().equals(5007))
.findFirst()
.get().getNodeOne()).isEqualTo("v1");
assertThat(actual.stream()
.filter(eg -> eg.getId().equals(5007))
.findFirst()
.get().getNodeTwo()).isEqualTo("v5");
actual.forEach(eg -> LOG.info("---- EDGE: " + eg));
}
@Test
public void getEdgeById() {
EdgeGraph actual = edgeService.getById(5005);
Assert.assertNotNull(actual);
Assert.assertEquals(TestUtils.edgeGraph, actual);
LOG.info(actual.toString());
}
@Test
public void getEdgeByIdNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"), "EDGE", 7000));
edgeService.getById(7000);
}
@Test
public void getEdgesByNodeName() {
Set<EdgeGraph> actual = edgeService.get("v1");
Set<EdgeGraph> expected = TestUtils.edgesGraph.stream()
.filter(eg -> eg.getId() != 5008)
.collect(Collectors.toSet());
Assert.assertFalse(actual.isEmpty());
Assert.assertEquals(expected.size(), actual.size());
assertThat(actual).containsAnyOf(expected.iterator().next());
actual.forEach(eg -> LOG.info("---- EDGE: " + eg));
}
@Test
public void getEdgesByNodeNameNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getEdgeMsg().get("EDGE_MSG_GET_BY_NAME_ERROR"), "v100"));
edgeService.get("v100");
}
@Test
public void getEdgeByNodeNameOneAndNodeNameTwo() {
EdgeGraph actual = edgeService.get("v1", "v2");
Assert.assertNotNull(actual);
Assert.assertEquals(TestUtils.edgeGraph, actual);
Integer actualId = actual.getId();
LOG.info(actual.toString());
actual = edgeService.get("v2", "v1");
Assert.assertEquals(actualId, actual.getId()); //must equal because graph is undirected
}
}

View File

@@ -0,0 +1,314 @@
package ru.resprojects.linkchecker.services;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import ru.resprojects.linkchecker.AppProperties;
import ru.resprojects.linkchecker.LinkcheckerApplication;
import ru.resprojects.linkchecker.model.Edge;
import ru.resprojects.linkchecker.model.Node;
import ru.resprojects.linkchecker.repositories.EdgeRepository;
import ru.resprojects.linkchecker.repositories.NodeRepository;
import ru.resprojects.linkchecker.util.GraphUtil;
import ru.resprojects.linkchecker.util.exeptions.ApplicationException;
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyIterable;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.when;
import static ru.resprojects.linkchecker.dto.GraphDto.EdgeGraph;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LinkcheckerApplication.class)
@ActiveProfiles(profiles = "moc_test")
public class GraphEdgeServiceMockTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@MockBean
private EdgeRepository edgeRepository;
@MockBean
private NodeRepository nodeRepository;
@Autowired
private AppProperties properties;
private GraphEdgeService edgeService;
private List<Node> nodes;
@Before
public void init() {
edgeService = new GraphEdgeServiceImpl(edgeRepository, nodeRepository, properties);
nodes = Stream.of(
new Node(5000, "v1", 0),
new Node(5001, "v2", 0),
new Node(5002, "v3", 0),
new Node(5003, "v4", 0),
new Node(5004, "v5", 0)
).collect(Collectors.toList());
}
@Test
public void createEdge() {
Node nodeOne = nodes.get(0);
Node nodeTwo = nodes.get(1);
Edge edge = new Edge(5005, nodeOne, nodeTwo);
EdgeGraph edgeGraph = new EdgeGraph("v1", "v2");
given(nodeRepository.getByName("v1")).willReturn(nodeOne);
given(nodeRepository.getByName("v2")).willReturn(nodeTwo);
when(edgeRepository.save(any(Edge.class))).thenReturn(edge);
EdgeGraph actual = edgeService.create(edgeGraph);
Assert.assertNotNull(actual);
Assert.assertEquals(new Integer(5005), actual.getId());
}
@Test
public void createEdgeNodeNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), "v1"));
EdgeGraph edgeGraph = new EdgeGraph("v1", "v2");
given(nodeRepository.getByName("v1")).willReturn(null);
edgeService.create(edgeGraph);
}
@Test
public void createEdgeNullArgumentException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_ARGUMENT_NULL"));
edgeService.create((EdgeGraph) null);
}
@Test
public void createEdges() {
List<Edge> edges = new ArrayList<>();
edges.add(new Edge(5005,nodes.get(0), nodes.get(1)));
edges.add(new Edge(5006,nodes.get(0), nodes.get(2)));
edges.add(new Edge(5007,nodes.get(0), nodes.get(4)));
edges.add(new Edge(5008,nodes.get(2), nodes.get(3)));
Set<EdgeGraph> edgeGraphs = edges.stream()
.map(e -> new EdgeGraph(e.getNodeOne().getName(), e.getNodeTwo().getName()))
.collect(Collectors.toSet());
given(nodeRepository.getByName("v1")).willReturn(nodes.get(0));
given(nodeRepository.getByName("v2")).willReturn(nodes.get(1));
given(nodeRepository.getByName("v3")).willReturn(nodes.get(2));
given(nodeRepository.getByName("v4")).willReturn(nodes.get(3));
given(nodeRepository.getByName("v5")).willReturn(nodes.get(4));
when(edgeRepository.saveAll(anyIterable())).thenReturn(edges);
Set<EdgeGraph> actual = edgeService.create(edgeGraphs);
Assert.assertNotNull(actual);
Assert.assertEquals(4, actual.size());
}
@Test
public void createEdgesNodeNotFoundException() {
List<Edge> edges = Stream.of(
new Edge(5005,nodes.get(0), nodes.get(1)),
new Edge(5006,nodes.get(0), nodes.get(2)),
new Edge(5007,nodes.get(0), nodes.get(4)),
new Edge(5008,nodes.get(2), nodes.get(3))
).collect(Collectors.toList());
Set<EdgeGraph> edgeGraphs = edges.stream()
.map(e -> new EdgeGraph(e.getNodeOne().getName(), e.getNodeTwo().getName()))
.collect(Collectors.toSet());
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), "v1"));
given(nodeRepository.getByName("v1")).willReturn(null);
edgeService.create(edgeGraphs);
}
@Test
public void createEdgesNullArgumentException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_ARGUMENT_NULL"));
edgeService.create((Set<EdgeGraph>) null);
}
@Test
public void createEdgesEmptyCollectionException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_COLLECTION_EMPTY"));
edgeService.create(new HashSet<>());
}
@Test
public void createEdgesCollectionContainNullException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_COLLECTION_CONTAIN_NULL"));
Set<EdgeGraph> edgeGraphs = new HashSet<>();
edgeGraphs.add(new EdgeGraph("v1", "v2"));
edgeGraphs.add(null);
edgeService.create(edgeGraphs);
}
@Test
public void deleteEdgeById() {
List<Edge> edges = Stream.of(
new Edge(5008, nodes.get(2), nodes.get(3))
).collect(Collectors.toList());
given(edgeRepository.existsById(new Integer(5005))).willReturn(true);
given(edgeRepository.findAll()).willReturn(edges);
edgeService.delete(5005);
Set<EdgeGraph> actual = edgeService.getAll();
Assert.assertEquals(1, actual.size());
}
@Test
public void deleteEdgeByIdNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"), "EDGE", 5050));
when(edgeRepository.existsById(5050)).thenReturn(false);
edgeService.delete(5050);
}
@Test
public void deleteEdgesByNodeName() {
List<Edge> edges = Stream.of(
new Edge(5005,nodes.get(0), nodes.get(1)),
new Edge(5006,nodes.get(0), nodes.get(2)),
new Edge(5007,nodes.get(0), nodes.get(4))
).collect(Collectors.toList());
List<Edge> edgesAfterDelete = Stream.of(
new Edge(5008, nodes.get(2), nodes.get(3))
).collect(Collectors.toList());
given(nodeRepository.getByName("v1")).willReturn(nodes.get(0));
given(edgeRepository.findEdgesByNodeOneOrNodeTwo(any(Node.class), any(Node.class))).willReturn(edges);
given(edgeRepository.findAll()).willReturn(edgesAfterDelete);
edgeService.delete("v1");
Set<EdgeGraph> actual = edgeService.getAll();
Assert.assertEquals(1, actual.size());
}
@Test
public void deleteEdgesByNodeNameNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getEdgeMsg().get("EDGE_MSG_GET_BY_NAME_ERROR"), "v1"));
List<Edge> emptyList = new ArrayList<>();
given(edgeRepository.findEdgesByNodeOneOrNodeTwo(any(Node.class), any(Node.class))).
willReturn(emptyList);
edgeService.delete("v1");
}
@Test
public void deleteEdgeByNodeNameOneAndNodeNameTwo() {
Edge edge = new Edge(5005,nodes.get(0), nodes.get(1));
given(nodeRepository.getByName("v1")).willReturn(nodes.get(0));
given(nodeRepository.getByName("v2")).willReturn(nodes.get(1));
given(edgeRepository.findEdgeByNodeOneAndNodeTwo(any(Node.class), any(Node.class))).
willReturn(Optional.of(edge));
edgeService.delete("v1", "v2");
}
@Test
public void deleteEdgeByNodeNameOneAndNodeNameTwoNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getEdgeMsg().get("EDGE_MSG_GET_ERROR"), "v1", "v2"));
edgeService.delete("v1", "v2");
}
@Test
public void getAllEdges() {
List<Edge> edges = Stream.of(
new Edge(5005, nodes.get(0), nodes.get(1)),
new Edge(5006, nodes.get(0), nodes.get(2)),
new Edge(5007, nodes.get(0), nodes.get(4)),
new Edge(5008, nodes.get(2), nodes.get(3))
).collect(Collectors.toList());
given(edgeRepository.findAll()).willReturn(edges);
Set<EdgeGraph> actual = edgeService.getAll();
EdgeGraph edgeGraph = GraphUtil.edgeToEdgeGraph(edges.get(2));
Assert.assertEquals(4, actual.size());
assertThat(actual).contains(edgeGraph);
assertThat(actual.stream()
.filter(eg -> eg.getId().equals(5007))
.findFirst()
.get().getNodeOne()).isEqualTo("v1");
assertThat(actual.stream()
.filter(eg -> eg.getId().equals(5007))
.findFirst()
.get().getNodeTwo()).isEqualTo("v5");
}
@Test
public void getEdgeById() {
Node nodeOne = nodes.get(0);
Node nodeTwo = nodes.get(1);
Edge edge = new Edge(5005, nodeOne, nodeTwo);
given(edgeRepository.findById(5005)).willReturn(Optional.of(edge));
EdgeGraph actual = edgeService.getById(5005);
Assert.assertEquals("v1", actual.getNodeOne());
Assert.assertEquals("v2", actual.getNodeTwo());
}
@Test
public void getEdgeByIdNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"), "EDGE", 7000));
edgeService.getById(7000);
}
@Test
public void getEdgesByNodeName() {
List<Edge> edges = Stream.of(
new Edge(5005, nodes.get(0), nodes.get(1)),
new Edge(5006, nodes.get(0), nodes.get(2)),
new Edge(5007, nodes.get(0), nodes.get(4))
).collect(Collectors.toList());
given(nodeRepository.getByName("v1")).willReturn(nodes.get(0));
given(edgeRepository.findEdgesByNodeOneOrNodeTwo(nodes.get(0),nodes.get(0)))
.willReturn(edges);
Set<EdgeGraph> actual = edgeService.get("v1");
EdgeGraph edgeGraph = GraphUtil.edgeToEdgeGraph(edges.get(0));
assertThat(actual).contains(edgeGraph);
assertThat(actual.stream()
.filter(eg -> eg.getId().equals(5005))
.findFirst()
.get().getNodeOne()).isEqualTo("v1");
assertThat(actual.stream()
.filter(eg -> eg.getId().equals(5005))
.findFirst()
.get().getNodeTwo()).isEqualTo("v2");
}
@Test
public void getEdgeByNodeOneAndNodeTwoNames() {
Node nodeOne = nodes.get(0);
Node nodeTwo = nodes.get(1);
Edge edge = new Edge(5005, nodeOne, nodeTwo);
given(nodeRepository.getByName("v1")).willReturn(nodeOne);
given(nodeRepository.getByName("v2")).willReturn(nodeTwo);
given(edgeRepository.findEdgeByNodeOneAndNodeTwo(nodeOne, nodeTwo)).willReturn(Optional.of(edge));
EdgeGraph actual = edgeService.get("v1", "v2");
Assert.assertNotNull(actual);
Assert.assertEquals("v1", actual.getNodeOne());
Assert.assertEquals("v2", actual.getNodeTwo());
}
@Test
public void exceptionWhileEdgeByNodeOneAndNodeTwoNames() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getEdgeMsg().get("EDGE_MSG_GET_ERROR"), "v1", "v2"));
edgeService.get("v1", "v2");
}
}

View File

@@ -0,0 +1,199 @@
package ru.resprojects.linkchecker.services;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.junit4.SpringRunner;
import ru.resprojects.linkchecker.AppProperties;
import ru.resprojects.linkchecker.LinkcheckerApplication;
import ru.resprojects.linkchecker.util.exeptions.ApplicationException;
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.IntStream;
import static org.assertj.core.api.Assertions.assertThat;
import static ru.resprojects.linkchecker.dto.GraphDto.NodeGraph;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LinkcheckerApplication.class)
@ActiveProfiles(profiles = {"test", "debug"})
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
config = @SqlConfig(encoding = "UTF-8"))
public class GraphNodeServiceH2DBTests {
private static final Logger LOG = LoggerFactory.getLogger(GraphNodeServiceH2DBTests.class);
@Rule
public ExpectedException thrown = ExpectedException.none();
@Autowired
GraphNodeService nodeService;
@Autowired
AppProperties properties;
@Test
public void getNodeByName() {
NodeGraph nodeGraph = nodeService.get("v1");
Assert.assertNotNull(nodeGraph);
Assert.assertEquals("v1", nodeGraph.getName());
LOG.info("NODE DTO: " + nodeGraph);
}
@Test
public void getNodeById() {
NodeGraph nodeGraph = nodeService.getById(5000);
Assert.assertNotNull(nodeGraph);
Assert.assertEquals("v1", nodeGraph.getName());
LOG.info("NODE DTO: " + nodeGraph);
}
@Test
public void getNodeByNameNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage( String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), "v11"));
nodeService.get("v11");
}
@Test
public void getNodeByIdNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"), "NODE", 5050));
nodeService.getById(5050);
}
@Test
public void getAllNodes() {
Set<NodeGraph> actual = nodeService.getAll();
Assert.assertEquals(5, actual.size());
assertThat(actual.stream()
.filter(eg -> eg.getId().equals(5000))
.findFirst()
.get().getName()).isEqualTo("v1");
actual.forEach(ng -> LOG.info("---- NODE: " + ng));
}
@Test
public void deleteNodeByNodeGraph() {
NodeGraph nodeGraph = new NodeGraph(5000, "v1", 0);
nodeService.delete(nodeGraph);
Set<NodeGraph> actual = nodeService.getAll();
Assert.assertEquals(4, actual.size());
}
@Test
public void deleteNodeByNodeGraphNotFoundException() {
NodeGraph nodeGraph = new NodeGraph(5020, "v1", 0);
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_BY_OBJECT_ERROR"), nodeGraph.toString()));
nodeService.delete(nodeGraph);
}
@Test
public void deleteNodeByNodeGraphAnotherNotFoundException() {
NodeGraph nodeGraph = new NodeGraph(5000, "v1", 1);
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_BY_OBJECT_ERROR"), nodeGraph.toString()));
nodeService.delete(nodeGraph);
}
@Test
public void deleteNodeByName() {
nodeService.delete("v1");
Set<NodeGraph> actual = nodeService.getAll();
Assert.assertEquals(4, actual.size());
}
@Test
public void deleteNodeByNameNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage( String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), "v10"));
nodeService.delete("v10");
}
@Test
public void deleteNodeById() {
nodeService.delete(5000);
Set<NodeGraph> actual = nodeService.getAll();
Assert.assertEquals(4, actual.size());
}
@Test
public void deleteNodeByIdNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"), "NODE", 5100));
nodeService.delete(5100);
}
@Test
public void deleteAllNodes() {
nodeService.deleteAll();
Set<NodeGraph> nodeGraphs = nodeService.getAll();
Assert.assertNotNull(nodeGraphs);
Assert.assertEquals(0, nodeGraphs.size());
}
@Test
public void createNode() {
NodeGraph nodeGraph = new NodeGraph("v6");
nodeService.create(nodeGraph);
NodeGraph actual = nodeService.get("v6");
Assert.assertNotNull(actual);
Set<NodeGraph> nodeGraphs = nodeService.getAll();
nodeGraphs.forEach(ng -> LOG.info("---- NODE: " + ng));
}
@Test
public void createNodes() {
Set<NodeGraph> nodeGraphs = new HashSet<>();
IntStream.range(1, 6).forEach(i -> {
nodeGraphs.add(new NodeGraph("w" + i));
});
nodeService.create(nodeGraphs);
Set<NodeGraph> actual = nodeService.getAll();
Assert.assertNotNull(actual);
Assert.assertEquals(10, actual.size());
Assert.assertEquals("w1", actual.stream()
.filter(ng -> "w1".equals(ng.getName()))
.findFirst().get().getName());
actual.forEach(ng -> LOG.info("---- NODE: " + ng));
}
@Test
public void createNodeNullArgumentException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_ARGUMENT_NULL"));
nodeService.create((NodeGraph) null);
}
@Test
public void nodeUpdate() {
NodeGraph nodeGraph = new NodeGraph(5000, "v1", 1);
nodeService.update(nodeGraph);
NodeGraph actual = nodeService.get("v1");
Assert.assertNotNull(actual);
Assert.assertEquals(1, actual.getCounter(), 0);
Set<NodeGraph> nodeGraphs = nodeService.getAll();
nodeGraphs.forEach(ng -> LOG.info("---- NODE: " + ng));
}
@Test
public void nodeUpdateNullArgumentException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_ARGUMENT_NULL"));
nodeService.update(null);
}
}

View File

@@ -0,0 +1,303 @@
package ru.resprojects.linkchecker.services;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import ru.resprojects.linkchecker.AppProperties;
import ru.resprojects.linkchecker.LinkcheckerApplication;
import ru.resprojects.linkchecker.model.Node;
import ru.resprojects.linkchecker.repositories.NodeRepository;
import ru.resprojects.linkchecker.util.GraphUtil;
import ru.resprojects.linkchecker.util.exeptions.ApplicationException;
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyIterable;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.when;
import static ru.resprojects.linkchecker.dto.GraphDto.NodeGraph;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LinkcheckerApplication.class)
@ActiveProfiles(profiles = "moc_test")
public class GraphNodeServiceMockTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private GraphNodeService graphNodeService;
@MockBean
private NodeRepository nodeRepository;
@Autowired
private AppProperties properties;
private List<Node> nodes = Stream.of(
new Node(5001, "v2", 0),
new Node(5002, "v3", 0),
new Node(5003, "v4", 0),
new Node(5004, "v5", 0)
).collect(Collectors.toList());
@Before
public void init() {
graphNodeService = new GraphNodeServiceImpl(nodeRepository, properties);
}
@Test
public void getNodeByName() {
given(nodeRepository.getByName("v1")).willReturn(
new Node(5000, "v1", 0)
);
NodeGraph actual = graphNodeService.get("v1");
assertThat(actual.getName()).isEqualTo("v1");
assertThat(actual.getCounter()).isEqualTo(0);
}
@Test
public void getNodeByNameNotFoundException() throws NotFoundException {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), "v1"));
graphNodeService.get("v1");
}
@Test
public void getNodeById() {
given(nodeRepository.findById(5000)).willReturn(
Optional.of(new Node(5000, "v1", 0))
);
NodeGraph actual = graphNodeService.getById(5000);
assertThat(actual.getName()).isEqualTo("v1");
assertThat(actual.getCounter()).isEqualTo(0);
}
@Test
public void getNodeByIdNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage( String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"), "NODE", 5000));
graphNodeService.getById(5000);
}
@Test
public void getAllNodes() {
List<Node> nodes = Stream.of(
new Node(5000, "v1", 0),
new Node(5001, "v2", 0),
new Node(5002, "v3", 0),
new Node(5003, "v4", 0),
new Node(5004, "v5", 0)
).collect(Collectors.toList());
given(nodeRepository.findAll()).willReturn(nodes);
Set<NodeGraph> actual = graphNodeService.getAll();
NodeGraph nodeGraph = GraphUtil.nodeToNodeGraph(nodes.get(4));
Assert.assertEquals(5, actual.size());
assertThat(actual).contains(nodeGraph);
}
@Test
public void deleteNodeByNodeGraph() {
given(nodeRepository.findById(5000)).willReturn(
Optional.of(new Node(5000, "v1", 0))
);
given(nodeRepository.findAll()).willReturn(nodes);
NodeGraph nodeGraph = new NodeGraph(5000, "v1", 0);
graphNodeService.delete(nodeGraph);
Set<NodeGraph> actual = graphNodeService.getAll();
Assert.assertEquals(4, actual.size());
}
@Test
public void deleteNodeByNodeGraphNotFoundException() {
NodeGraph nodeGraph = new NodeGraph(5000, "v1", 0);
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_BY_OBJECT_ERROR"), nodeGraph.toString()));
graphNodeService.delete(nodeGraph);
}
@Test
public void deleteNodeByNodeGraphWithNullIdNotFoundException() {
NodeGraph nodeGraph = new NodeGraph(null, "v1", 0);
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_BY_OBJECT_ERROR"), nodeGraph.toString()));
graphNodeService.delete(nodeGraph);
}
@Test
public void deleteNodeByNodeGraphNullArgumentException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_ARGUMENT_NULL"));
graphNodeService.delete((NodeGraph) null);
}
@Test
public void deleteNodeByName() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), "v1"));
when(nodeRepository.existsByName("v1")).thenReturn(true).thenReturn(false);
given(nodeRepository.findAll()).willReturn(nodes);
graphNodeService.delete("v1");
Set<NodeGraph> actual = graphNodeService.getAll();
Assert.assertEquals(4, actual.size());
graphNodeService.delete("v1");
}
@Test
public void deleteNodeByNameNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), "null"));
graphNodeService.delete((String) null);
}
@Test
public void deleteNodeById() {
thrown.expect(NotFoundException.class);
thrown.expectMessage( String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"), "NODE", 5000));
when(nodeRepository.existsById(5000)).thenReturn(true).thenReturn(false);
given(nodeRepository.findAll()).willReturn(nodes);
graphNodeService.delete(5000);
Set<NodeGraph> actual = graphNodeService.getAll();
Assert.assertEquals(4, actual.size());
graphNodeService.delete(5000);
}
@Test
public void deleteNodeByIdNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage( String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"), "NODE", null));
graphNodeService.delete((Integer) null);
}
@Test
public void createNode() {
NodeGraph nodeGraph = new NodeGraph("v1");
Node node = new Node("v1");
node.setId(5000);
when(nodeRepository.save(any(Node.class))).thenReturn(node);
NodeGraph actual = graphNodeService.create(nodeGraph);
Assert.assertNotNull(actual);
Assert.assertNotNull(actual.getId());
Assert.assertEquals(5000, actual.getId().intValue());
Assert.assertEquals("v1", actual.getName());
}
@Test
public void createNodeIsPresentException() {
thrown.expect(ApplicationException.class);
NodeGraph nodeGraph = new NodeGraph("v1");
thrown.expectMessage(String.format(
properties.getNodeMsg().get("NODE_MSG_ALREADY_PRESENT_ERROR"),
nodeGraph.getName()
));
Node node = new Node("v1");
node.setId(5000);
when(nodeRepository.getByName(any(String.class))).thenReturn(node);
graphNodeService.create(nodeGraph);
}
@Test
public void createNodeNullArgumentException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_ARGUMENT_NULL"));
graphNodeService.create((NodeGraph) null);
}
@Test
public void createNodes() {
Set<NodeGraph> nodeGraphs = new HashSet<>();
List<Node> nodes = new ArrayList<>();
IntStream.range(1, 6).forEach(i -> {
nodeGraphs.add(new NodeGraph("w" + i));
nodes.add(new Node(5000 + i, "w" + i, 0));
});
when(nodeRepository.saveAll(anyIterable())).thenReturn(nodes);
Set<NodeGraph> actual = graphNodeService.create(nodeGraphs);
Assert.assertNotNull(actual);
Assert.assertEquals(5, actual.size());
}
@Test
public void createNodesIsPresentException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(String.format(
properties.getNodeMsg().get("NODE_MSG_ALREADY_PRESENT_ERROR"),
"w1"
));
Set<NodeGraph> nodeGraphs = new HashSet<>();
nodeGraphs.add(new NodeGraph("w1"));
Node node = new Node(5000, "w1", 0);
when(nodeRepository.getByName(any(String.class))).thenReturn(node);
graphNodeService.create(nodeGraphs);
}
@Test
public void createNodesNullArgumentException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_ARGUMENT_NULL"));
graphNodeService.create((Set<NodeGraph>) null);
}
@Test
public void createNodesEmptyCollectionException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_COLLECTION_EMPTY"));
graphNodeService.create(new HashSet<>());
}
@Test
public void createNodesCollectionContainNullException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_COLLECTION_CONTAIN_NULL"));
Set<NodeGraph> nodeGraphs = new HashSet<>();
nodeGraphs.add(new NodeGraph("v1"));
nodeGraphs.add(null);
graphNodeService.create(nodeGraphs);
}
@Test
public void updateNode() {
NodeGraph nodeGraph = new NodeGraph(5000, "v1", 2);
Node node = new Node(5000, "v1", 2);
when(nodeRepository.save(any(Node.class))).thenReturn(node);
when(nodeRepository.findById(5000)).thenReturn(Optional.of(node));
graphNodeService.update(nodeGraph);
NodeGraph actual = graphNodeService.getById(5000);
Assert.assertNotNull(actual);
Assert.assertEquals(nodeGraph, GraphUtil.nodeToNodeGraph(node));
}
@Test
public void updateNodeNullArgumentException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_ARGUMENT_NULL"));
graphNodeService.update(null);
}
@Test
public void updateNodeWhileUpdateException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_UPDATE_ERROR"), 5000));
NodeGraph nodeGraph = new NodeGraph(5000, "v1", 0);
when(nodeRepository.save(any(Node.class))).thenReturn(null);
graphNodeService.update(nodeGraph);
}
}

View File

@@ -0,0 +1,194 @@
package ru.resprojects.linkchecker.services;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.junit4.SpringRunner;
import ru.resprojects.linkchecker.AppProperties;
import ru.resprojects.linkchecker.LinkcheckerApplication;
import ru.resprojects.linkchecker.dto.GraphDto;
import ru.resprojects.linkchecker.util.exeptions.ApplicationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static ru.resprojects.linkchecker.dto.GraphDto.EdgeGraph;
import static ru.resprojects.linkchecker.dto.GraphDto.NodeGraph;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LinkcheckerApplication.class)
@ActiveProfiles(profiles = {"test", "debug"})
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
config = @SqlConfig(encoding = "UTF-8"))
public class GraphServiceH2DBTests {
private static final Logger LOG = LoggerFactory.getLogger(GraphServiceH2DBTests.class);
@Rule
public ExpectedException thrown = ExpectedException.none();
@Autowired
private GraphService graphService;
@Autowired
private AppProperties properties;
private Set<EdgeGraph> edgeGraphSet = Stream.of(
new EdgeGraph("v1", "v2"),
new EdgeGraph("v1", "v3"),
new EdgeGraph("v1", "v4"),
new EdgeGraph("v2", "v3"),
new EdgeGraph("v2", "v4"),
new EdgeGraph("v3", "v4")
).collect(Collectors.toSet());
@Test
public void createGraph() {
GraphDto graphDto = new GraphDto();
graphDto.setNodes(Stream.of(
new NodeGraph("v1"),
new NodeGraph("v2"),
new NodeGraph("v3"),
new NodeGraph("v4")
).collect(Collectors.toSet()));
graphDto.setEdges(edgeGraphSet);
GraphDto actual = graphService.create(graphDto);
Assert.assertNotNull(actual);
LOG.info(actual.toString());
}
@Test
public void createGraphWithExtraEdges() {
GraphDto graphDto = new GraphDto();
graphDto.setNodes(Stream.of(
new NodeGraph("v1"),
new NodeGraph("v2"),
new NodeGraph("v3")
).collect(Collectors.toSet()));
graphDto.setEdges(edgeGraphSet);
GraphDto actual = graphService.create(graphDto);
Assert.assertNotNull(actual);
LOG.info(actual.toString());
}
@Test
public void createGraphNullArgumentException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_ARGUMENT_NULL"));
graphService.create(null);
}
@Test
public void createGraphEmptyCollectionException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage("NODES: " + properties.getAppMsg().get("MSG_COLLECTION_EMPTY"));
GraphDto graphDto = new GraphDto();
graphDto.setNodes(new HashSet<>());
graphDto.setEdges(edgeGraphSet);
graphService.create(graphDto);
}
@Test
public void getGraphWithRemovedEdges() {
graphService.getEdges().create(Stream.of(
new EdgeGraph("v2", "v3"),
new EdgeGraph("v3", "v5"),
new EdgeGraph("v2", "v4"),
new EdgeGraph("v5", "v4")
).collect(Collectors.toSet()));
Set<EdgeGraph> edgeGraphs = graphService.getEdges().getAll();
LOG.info(edgeGraphs.toString());
GraphDto actual = graphService.get();
Assert.assertNotNull(actual);
Assert.assertNotEquals(edgeGraphs.size(), actual.getEdges().size());
LOG.info(actual.toString());
}
@Test
public void getGraphWithoutRemovingEdges() {
GraphDto actual = graphService.get();
Assert.assertNotNull(actual);
Assert.assertEquals(4, actual.getEdges().size());
LOG.info(actual.toString());
}
@Test
public void deleteGraph() {
graphService.clear();
GraphDto actual = graphService.get();
Assert.assertNotNull(actual);
Assert.assertEquals(0, actual.getEdges().size());
Assert.assertEquals(0, actual.getNodes().size());
}
@Test
public void getGraphAfterAddedNewNode() {
graphService.getNodes().create(new NodeGraph("v6"));
GraphDto actual = graphService.get();
Assert.assertNotNull(actual);
Assert.assertEquals(6, actual.getNodes().size());
Assert.assertEquals(4, actual.getEdges().size());
}
@Test
public void checkNodesRoute() {
Set<String> nodeNames = Stream.of("v1", "v2", "v3", "v5").collect(Collectors.toSet());
int faultCount = 0;
Map<String, Integer> nodeErrorStat = new HashMap<>();
for (int i = 0; i < 100; i++) {
try {
LOG.info(graphService.checkRoute(nodeNames));
} catch (ApplicationException e) {
String node = e.getMessage().split(" ")[1];
LOG.info(node);
if (!nodeErrorStat.containsKey(node)) {
nodeErrorStat.put(node, 1);
} else {
Integer val = nodeErrorStat.get(node);
nodeErrorStat.put(node, ++val);
}
faultCount++;
}
}
LOG.info(graphService.get().toString());
LOG.info("FAULT COUNT for CHECK ROUTE = " + faultCount);
nodeErrorStat.forEach((key, value) -> LOG.info("NODE " + key + " error count = " + value));
}
@Test
public void checkRouteNullCollectionException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_ARGUMENT_NULL"));
graphService.checkRoute(null);
}
@Test
public void checkRouteEmptyCollectionException() {
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_COLLECTION_EMPTY"));
graphService.checkRoute(new HashSet<>());
}
@Test
public void checkRouteCollectionHaveOneElementException() {
Set<String> nodeNames = Stream.of("v10").collect(Collectors.toSet());
thrown.expect(ApplicationException.class);
thrown.expectMessage(properties.getAppMsg().get("MSG_COLLECTION_CONTAIN_ONE_ELEMENT"));
graphService.checkRoute(nodeNames);
}
}

View File

@@ -0,0 +1,110 @@
package ru.resprojects.linkchecker.services;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.junit4.PowerMockRunnerDelegate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import ru.resprojects.linkchecker.AppProperties;
import ru.resprojects.linkchecker.LinkcheckerApplication;
import ru.resprojects.linkchecker.TestUtils;
import ru.resprojects.linkchecker.util.GraphUtil;
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.when;
import static org.powermock.api.mockito.PowerMockito.spy;
//How to use PowerMock https://www.baeldung.com/intro-to-powermock
//How to use PowerMock and SpringRunner https://stackoverflow.com/a/57780838
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@SpringBootTest(classes = LinkcheckerApplication.class)
@ActiveProfiles(profiles = "moc_test")
@PrepareForTest(GraphUtil.class)
@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*",
"javax.xml.transform.*", "org.xml.*", "javax.management.*",
"javax.net.ssl.*", "com.sun.org.apache.xalan.internal.xsltc.trax.*"})
public class GraphServiceMockTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private GraphService graphService;
@MockBean
private GraphEdgeService edgeService;
@MockBean
private GraphNodeService nodeService;
@Autowired
private AppProperties properties;
@Before
public void init() {
graphService = new GraphServiceImpl(edgeService, nodeService, properties);
}
@Test
public void checkRouteNodeFaultException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_IS_FAULT"), "v1"));
spy(GraphUtil.class);
Map<String, Boolean> nodesFault = new HashMap<>();
nodesFault.put("v1", true);
nodesFault.put("v2", false);
nodesFault.put("v3", false);
given(nodeService.getAll()).willReturn(TestUtils.nodesGraph);
given(edgeService.getAll()).willReturn(TestUtils.edgesGraph);
when(GraphUtil.getRandomNodeFault(anyCollection())).thenReturn(nodesFault);
graphService.checkRoute(Stream.of("v1", "v2", "v3").collect(Collectors.toSet()));
}
@Test
public void checkRouteNodeNotReachableException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_NOT_REACHABLE"), "v1", "v4"));
spy(GraphUtil.class);
Map<String, Boolean> nodesFault = new HashMap<>();
nodesFault.put("v1", false);
nodesFault.put("v2", false);
nodesFault.put("v3", false);
nodesFault.put("v4", false);
given(nodeService.getAll()).willReturn(TestUtils.nodesGraph);
given(edgeService.getAll()).willReturn(TestUtils.edgesGraph);
when(GraphUtil.getRandomNodeFault(anyCollection())).thenReturn(nodesFault);
graphService.checkRoute(Stream.of("v1", "v2", "v4").collect(Collectors.toSet()));
}
@Test
public void checkRouteNodeNotFoundException() {
thrown.expect(NotFoundException.class);
thrown.expectMessage(String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), "v7"));
spy(GraphUtil.class);
Map<String, Boolean> nodesFault = new HashMap<>();
nodesFault.put("v1", false);
nodesFault.put("v2", false);
nodesFault.put("v3", false);
given(nodeService.getAll()).willReturn(TestUtils.nodesGraph);
given(edgeService.getAll()).willReturn(TestUtils.edgesGraph);
when(GraphUtil.getRandomNodeFault(anyCollection())).thenReturn(nodesFault);
graphService.checkRoute(Stream.of("v1", "v2", "v3", "v7").collect(Collectors.toSet()));
}
}

View File

@@ -0,0 +1,332 @@
package ru.resprojects.linkchecker.util;
import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultEdge;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import ru.resprojects.linkchecker.LinkcheckerApplication;
import ru.resprojects.linkchecker.dto.GraphDto;
import ru.resprojects.linkchecker.model.Edge;
import ru.resprojects.linkchecker.model.Node;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static ru.resprojects.linkchecker.dto.GraphDto.NodeGraph;
import static ru.resprojects.linkchecker.dto.GraphDto.EdgeGraph;
import static ru.resprojects.linkchecker.util.GraphUtil.*;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LinkcheckerApplication.class)
@ActiveProfiles(profiles = "moc_test")
public class GraphUtilTests {
private static final Logger LOG = LoggerFactory.getLogger(GraphUtilTests.class);
private GraphDto graphDto;
@Before
public void init() {
Set<NodeGraph> nodeGraphSet = Stream.of(
new NodeGraph(5000, "v1", 0),
new NodeGraph(5001, "v2", 0),
new NodeGraph(5002, "v3", 0),
new NodeGraph(5003, "v4", 0),
new NodeGraph(5004, "v5", 0)
).collect(Collectors.toSet());
Set<EdgeGraph> edgeGraphSet = Stream.of(
new EdgeGraph(5005, "v1", "v2"),
new EdgeGraph(5006, "v1", "v3"),
new EdgeGraph(5007, "v1", "v5"),
new EdgeGraph(5008, "v3", "v4")
).collect(Collectors.toSet());
graphDto = new GraphDto(nodeGraphSet, edgeGraphSet);
}
@Test
public void generateRandomNodeFaultTest() {
Set<NodeGraph> nodeGraphSet = new HashSet<>();
IntStream.range(0, 23).forEach(v -> nodeGraphSet.add(new NodeGraph(5000 + v, "v" + v, 0)));
Map<String, Boolean> result = getRandomNodeFault(nodeGraphSet);
Assert.assertNotNull(result);
result.forEach((key, value) -> LOG.debug("Node: " + key + " is fault = " + value));
long countOfFault = result.entrySet().stream().filter(Map.Entry::getValue).count();
LOG.debug("Count of fault elements = " + countOfFault);
}
@Test
public void returnEmptyMapFromGenerateRandomNodeFault() {
Map<String, Boolean> result = getRandomNodeFault(null);
Assert.assertTrue(result.isEmpty());
result = getRandomNodeFault(new HashSet<>());
Assert.assertTrue(result.isEmpty());
}
@Test
public void getEdgesFromGraphDtoTest() {
Set<Edge> actual = getEdgesFromGraphDto(graphDto);
Assert.assertNotNull(actual);
assertThat(actual).isNotEmpty();
Assert.assertEquals(graphDto.getEdges().size(), actual.size());
Assert.assertTrue(actual.stream()
.anyMatch(edge -> edge.getId().equals(
graphDto.getEdges().iterator().next().getId())
)
);
actual.forEach(edge -> LOG.debug(edge.toString()));
}
@Test
public void getEdgesFromGraphDtoSkipEdgeGraph() {
EdgeGraph eg = new EdgeGraph(5009, "v3", "v7");
graphDto.getEdges().add(eg);
Set<Edge> actual = getEdgesFromGraphDto(graphDto);
Assert.assertNotNull(actual);
Assert.assertTrue(actual.stream().noneMatch(
edge -> edge.getId().equals(eg.getId()))
);
LOG.debug("EdgeGraph collection:");
graphDto.getEdges().forEach(edgeGraph -> LOG.debug(edgeGraph.toString()));
LOG.debug("Edge collection:");
actual.forEach(edge -> LOG.debug(edge.toString()));
}
@Test
public void edgeToEdgeGraphTest() {
Edge edge = new Edge(3, new Node(1, "v1", 0),
new Node(2, "v2", 0));
EdgeGraph actual = edgeToEdgeGraph(edge);
Assert.assertNotNull(actual);
Assert.assertEquals(edge.getId(), actual.getId());
}
@Test
public void edgeToEdgeGraphReturnNull() {
Assert.assertNull(edgeToEdgeGraph(null));
}
@Test
public void edgesToEdgeGraphsTest() {
Set<Edge> edges = Stream.of(
new Edge(3, new Node(1, "v1", 0),
new Node(2, "v2", 0)),
new Edge(6, new Node(4, "v1", 0),
new Node(5, "v3", 0))
).collect(Collectors.toSet());
Set<EdgeGraph> actual = edgesToEdgeGraphs(edges);
Assert.assertNotNull(actual);
assertThat(actual).isNotEmpty();
Assert.assertEquals(edges.size(), actual.size());
for (EdgeGraph edgeGraph : actual) {
Assert.assertTrue(edges.stream()
.anyMatch(e -> e.getId().equals(edgeGraph.getId())));
}
}
@Test
public void edgesToEdgeGraphsReturnEmptyCollection() {
assertThat(edgesToEdgeGraphs(null)).isEmpty();
}
@Test
public void nodeGraphToNodeTest() {
NodeGraph nodeGraph = graphDto.getNodes().iterator().next();
Node node = nodeGraphToNode(nodeGraph);
Assert.assertNotNull(node);
Assert.assertEquals(nodeGraph.getId(), node.getId());
}
@Test
public void nodeGraphToNodeReturnNull() {
Assert.assertNull(nodeGraphToNode(null));
}
@Test
public void nodeToNodeGraphTest() {
Node node = new Node(1, "v1", 0);
NodeGraph actual = nodeToNodeGraph(node);
Assert.assertNotNull(actual);
Assert.assertEquals(node.getId(), node.getId());
}
@Test
public void nodeToNodeGraphReturnNull() {
Assert.assertNull(nodeToNodeGraph(null));
}
@Test
public void nodeGraphsToNodesTest() {
Set<Node> actual = nodeGraphsToNodes(graphDto.getNodes());
Assert.assertNotNull(actual);
assertThat(actual).isNotEmpty();
Assert.assertEquals(graphDto.getNodes().size(), actual.size());
for (Node node : actual) {
Assert.assertTrue(graphDto.getNodes().stream()
.anyMatch(n -> n.getId().equals(node.getId())));
}
}
@Test
public void nodeGraphsToNodesReturnEmptyCollection() {
assertThat(nodeGraphsToNodes(null)).isEmpty();
}
@Test
public void nodesToNodeGraphsTest() {
Set<Node> nodes = Stream.of(
new Node(1, "v1", 0),
new Node(2, "v2", 0)
).collect(Collectors.toSet());
Set<NodeGraph> actual = nodesToNodeGraphs(nodes);
Assert.assertNotNull(actual);
assertThat(actual).isNotEmpty();
for (NodeGraph nodeGraph : actual) {
Assert.assertTrue(nodes.stream()
.anyMatch(n -> n.getId().equals(nodeGraph.getId())));
}
}
@Test
public void nodesToNodeGraphsReturnEmptyCollection() {
assertThat(nodesToNodeGraphs(null)).isEmpty();
}
@Test
public void exportToGraphVizTest() {
String actual = exportToGraphViz(graphDto);
assertThat(actual).isNotEmpty();
assertThat(actual).contains("strict graph");
}
@Test
public void exportToGraphVizReturnNull() {
assertThat(exportToGraphViz(null)).isEmpty();
}
@Test
public void graphBuilderTest() {
Graph<Node, DefaultEdge> actual = graphBuilder(graphDto.getNodes(),
graphDto.getEdges());
Assert.assertNotNull(actual);
Assert.assertEquals(actual.vertexSet().size(), graphDto.getNodes().size());
Assert.assertEquals(actual.edgeSet().size(), graphDto.getEdges().size());
LOG.debug(actual.toString());
}
@Test
public void graphBuilderSkipNullElementFromNodesAndEdges() {
graphDto.getNodes().add(null);
graphDto.getEdges().add(null);
Graph<Node, DefaultEdge> actual = graphBuilder(graphDto.getNodes(),
graphDto.getEdges());
Assert.assertNotNull(actual);
Assert.assertNotEquals(actual.vertexSet().size(), graphDto.getNodes().size());
Assert.assertNotEquals(actual.edgeSet().size(), graphDto.getEdges().size());
LOG.debug(actual.toString());
}
@Test
public void graphBuilderSkipEdgeWithNonExistsNodes() {
graphDto.getEdges().add(new EdgeGraph(5009, "v10", "v12"));
Graph<Node, DefaultEdge> actual = graphBuilder(graphDto.getNodes(),
graphDto.getEdges());
Assert.assertNotNull(actual);
Assert.assertNotEquals(actual.edgeSet().size(), graphDto.getEdges().size());
LOG.debug(actual.toString());
}
@Test
public void graphBuilderReturnEmptyGraph() {
Graph<Node, DefaultEdge> actual = graphBuilder(null,
graphDto.getEdges());
assertThat(actual.vertexSet()).isEmpty();
assertThat(actual.edgeSet()).isEmpty();
LOG.debug(actual.toString());
actual = graphBuilder(graphDto.getNodes(), null);
assertThat(actual.vertexSet()).isEmpty();
assertThat(actual.edgeSet()).isEmpty();
LOG.debug(actual.toString());
actual = graphBuilder(null, null);
assertThat(actual.vertexSet()).isEmpty();
assertThat(actual.edgeSet()).isEmpty();
LOG.debug(actual.toString());
}
@Test
public void graphToGraphDtoTest() {
GraphDto actual = graphToGraphDto(graphBuilder(graphDto.getNodes(),
graphDto.getEdges()));
assertThat(actual.getNodes()).isNotEmpty();
assertThat(actual.getEdges()).isNotEmpty();
Assert.assertEquals(graphDto.getNodes().size(), actual.getNodes().size());
Assert.assertEquals(graphDto.getEdges().size(), actual.getEdges().size());
for (NodeGraph nodeGraph : actual.getNodes()) {
Assert.assertTrue(graphDto.getNodes().stream()
.anyMatch(ng -> ng.equals(nodeGraph)));
}
// Because method graphToGraphDto is return edges without IDs ,
// IDs is not checked.
for (EdgeGraph edgeGraph : actual.getEdges()) {
Assert.assertTrue(graphDto.getEdges().stream()
.anyMatch(eg -> eg.getNodeOne().equals(edgeGraph.getNodeOne())
&& eg.getNodeTwo().equals(edgeGraph.getNodeTwo()))
);
}
LOG.debug(actual.toString());
}
@Test
public void graphToGraphDtoReturnEmptyGraphDto() {
GraphDto actual = graphToGraphDto(null);
assertThat(actual.getNodes()).isEmpty();
assertThat(actual.getEdges()).isEmpty();
}
@Test
public void removeCyclesFromGraphTest() {
GraphDto cyclesGraph = new GraphDto();
cyclesGraph.getNodes().addAll(graphDto.getNodes());
cyclesGraph.getEdges().addAll(graphDto.getEdges());
cyclesGraph.getEdges().add(new EdgeGraph(5009, "v2", "v4"));
cyclesGraph.getEdges().add(new EdgeGraph(5010, "v2", "v3"));
cyclesGraph.getEdges().add(new EdgeGraph(5011, "v3", "v5"));
cyclesGraph.getEdges().add(new EdgeGraph(5012, "v3", "v4"));
cyclesGraph.getEdges().add(new EdgeGraph(5013, "v5", "v4"));
GraphDto actual = graphToGraphDto(removeCyclesFromGraph(graphBuilder(
cyclesGraph.getNodes(), cyclesGraph.getEdges())));
Assert.assertEquals(graphDto.getNodes().size(), actual.getNodes().size());
Assert.assertEquals(graphDto.getEdges().size(), actual.getEdges().size());
}
@Test
public void removeCyclesFromGraphCyclesNotFound() {
GraphDto actual = graphToGraphDto(removeCyclesFromGraph(graphBuilder(
graphDto.getNodes(), graphDto.getEdges())));
for (NodeGraph nodeGraph : actual.getNodes()) {
Assert.assertTrue(graphDto.getNodes().stream()
.anyMatch(ng -> ng.equals(nodeGraph)));
}
// Because method graphToGraphDto is return edges without IDs ,
// IDs is not checked.
for (EdgeGraph edgeGraph : actual.getEdges()) {
Assert.assertTrue(graphDto.getEdges().stream()
.anyMatch(eg -> eg.getNodeOne().equals(edgeGraph.getNodeOne())
&& eg.getNodeTwo().equals(edgeGraph.getNodeTwo()))
);
}
}
}

View File

@@ -0,0 +1,310 @@
package ru.resprojects.linkchecker.web.rest;
import com.google.gson.reflect.TypeToken;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import ru.resprojects.linkchecker.AppProperties;
import ru.resprojects.linkchecker.LinkcheckerApplication;
import ru.resprojects.linkchecker.TestUtils;
import ru.resprojects.linkchecker.util.exeptions.ErrorInfo;
import ru.resprojects.linkchecker.util.exeptions.ErrorPlaceType;
import ru.resprojects.linkchecker.util.exeptions.ErrorType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static ru.resprojects.linkchecker.dto.GraphDto.EdgeGraph;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LinkcheckerApplication.class)
@ActiveProfiles(profiles = {"test", "debug"})
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
config = @SqlConfig(encoding = "UTF-8"))
@AutoConfigureMockMvc
public class GraphEdgeRestControllerTests {
private static final Logger LOG = LoggerFactory.getLogger(GraphRestControllerTests.class);
@Autowired
private MockMvc mvc;
@Autowired
private AppProperties properties;
@Test
public void addNewEdge() throws Exception {
EdgeGraph newEdge = new EdgeGraph("v1", "v4");
MvcResult result = this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newEdge))).andReturn();
Assert.assertEquals(HttpStatus.CREATED.value(), result.getResponse().getStatus());
EdgeGraph returnedEdge = TestUtils.mapFromJson(result.getResponse().getContentAsString(), EdgeGraph.class);
Assert.assertNotNull(returnedEdge);
Assert.assertEquals(newEdge.getNodeOne(), returnedEdge.getNodeOne());
Assert.assertEquals(newEdge.getNodeTwo(), returnedEdge.getNodeTwo());
Assert.assertNotNull(returnedEdge.getId());
}
@Test
public void addNewEdgeValidationException() throws Exception {
MvcResult result = this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(new EdgeGraph()))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.VALIDATION_ERROR, error.getType());
Assert.assertEquals(ErrorPlaceType.APP, error.getPlace());
LOG.info(Arrays.asList(error.getMessages()).toString());
}
@Test
public void addNewEdgeAlreadyPresentException() throws Exception {
EdgeGraph newEdge = new EdgeGraph("v1", "v2");
MvcResult result = this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newEdge))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_ERROR, error.getType());
Assert.assertEquals(ErrorPlaceType.EDGE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(properties.getEdgeMsg().get("EDGE_MSG_ALREADY_PRESENT_ERROR"),
newEdge.getNodeOne(), newEdge.getNodeTwo(),
newEdge.getNodeTwo(), newEdge.getNodeOne())));
LOG.info(errMsgs.toString());
}
@Test
public void addNewEdges() throws Exception {
Set<EdgeGraph> newEdges = Stream.of(
new EdgeGraph("v1", "v4"),
new EdgeGraph("v2", "v4")
).collect(Collectors.toSet());
MvcResult result = this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newEdges))).andReturn();
Assert.assertEquals(HttpStatus.CREATED.value(), result.getResponse().getStatus());
Type listType = new TypeToken<HashSet<EdgeGraph>>() {}.getType();
Set<EdgeGraph> returnedEdges = TestUtils.mapFromJson(result.getResponse().getContentAsString(), listType);
Assert.assertEquals(newEdges.size(), returnedEdges.size());
Assert.assertTrue(returnedEdges.stream().anyMatch(ng -> ng.getNodeOne().equals("v1") && ng.getNodeTwo().equals("v4")));
}
@Test
public void addNewEdgesEmptyCollectionException() throws Exception {
MvcResult result = this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(Collections.emptySet()))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_ERROR, error.getType());
Assert.assertEquals(ErrorPlaceType.EDGE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(properties.getAppMsg().get("MSG_COLLECTION_EMPTY")));
LOG.info(errMsgs.toString());
}
@Test
public void addNewEdgesCollectionContainNullObjectException() throws Exception {
Set<EdgeGraph> newEdges = Stream.of(
null,
new EdgeGraph("v1", "v4"),
new EdgeGraph("v2", "v4")
).collect(Collectors.toSet());
MvcResult result = this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newEdges))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_ERROR, error.getType());
Assert.assertEquals(ErrorPlaceType.EDGE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(properties.getAppMsg().get("MSG_COLLECTION_CONTAIN_NULL")));
LOG.info(errMsgs.toString());
}
@Test
public void addNewEdgesCollectionContainAlreadyPresentNodeException() throws Exception {
EdgeGraph newEdge = new EdgeGraph("v1", "v2");
Set<EdgeGraph> newEdges = Stream.of(
newEdge,
new EdgeGraph("v1", "v4"),
new EdgeGraph("v2", "v4")
).collect(Collectors.toSet());
MvcResult result = this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newEdges))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_ERROR, error.getType());
Assert.assertEquals(ErrorPlaceType.EDGE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(properties.getEdgeMsg().get("EDGE_MSG_ALREADY_PRESENT_ERROR"),
newEdge.getNodeOne(), newEdge.getNodeTwo(),
newEdge.getNodeTwo(), newEdge.getNodeOne())));
LOG.info(errMsgs.toString());
}
@Test
public void getEdges() throws Exception {
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(TestUtils.edgesGraph)));
}
@Test
public void getEdgeById() throws Exception {
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL + "/byId/5005").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(TestUtils.edgeGraph)));
}
@Test
public void getEdgesByNodeName() throws Exception {
Set<EdgeGraph> expected = TestUtils.edgesGraph.stream()
.filter(eg -> eg.getId() != 5008)
.collect(Collectors.toSet());
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL + "/byName/v1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(expected)));
}
@Test
public void getEdgeByNodeNames() throws Exception {
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL + "/byName?nodeOne=v1&nodeTwo=v2").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(TestUtils.edgeGraph)));
}
@Test
public void getEdgeByIdNotFoundException() throws Exception {
MvcResult result = this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL + "/byId/5050")
.accept(MediaType.APPLICATION_JSON)).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_NOT_FOUND, error.getType());
Assert.assertEquals(ErrorPlaceType.EDGE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"), ErrorPlaceType.EDGE, 5050)));
}
@Test
public void getEdgesByNameNotFoundException() throws Exception {
MvcResult result = this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL + "/byName/v100")
.accept(MediaType.APPLICATION_JSON)).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_NOT_FOUND, error.getType());
Assert.assertEquals(ErrorPlaceType.EDGE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(properties.getEdgeMsg().get("EDGE_MSG_GET_BY_NAME_ERROR"), "v100")));
}
@Test
public void deleteAllEdges() throws Exception {
this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(Collections.EMPTY_SET)));
}
@Test
public void deleteEdgeById() throws Exception {
Set<EdgeGraph> expected = TestUtils.edgesGraph.stream()
.filter(eg -> eg.getId() != 5005)
.collect(Collectors.toSet());
this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL + "/byId/5005").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(expected)));
}
@Test
public void deleteEdgeByIdNotFoundException() throws Exception {
MvcResult result = this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL + "/byId/5050")
.accept(MediaType.APPLICATION_JSON)).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_NOT_FOUND, error.getType());
Assert.assertEquals(ErrorPlaceType.EDGE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"), ErrorPlaceType.EDGE, 5050)));
}
@Test
public void deleteEdgesByNodeName() throws Exception {
Set<EdgeGraph> expected = TestUtils.edgesGraph.stream()
.filter(eg -> eg.getId() != 5008)
.collect(Collectors.toSet());
this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL + "/byName/v4").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(expected)));
}
@Test
public void deleteEdgesByNodeNameNotFoundException() throws Exception {
MvcResult result = this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL + "/byName/v50")
.accept(MediaType.APPLICATION_JSON)).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_NOT_FOUND, error.getType());
Assert.assertEquals(ErrorPlaceType.EDGE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(properties.getEdgeMsg().get("EDGE_MSG_GET_BY_NAME_ERROR"), "v50")));
}
@Test
public void deleteEdgeByNodeNames() throws Exception {
Set<EdgeGraph> expected = TestUtils.edgesGraph.stream()
.filter(eg -> eg.getId() != 5005)
.collect(Collectors.toSet());
this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL + "/byName?nodeOne=v1&nodeTwo=v2").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(expected)));
}
@Test
public void deleteEdgeByNodeNamesNotFoundException() throws Exception {
MvcResult result = this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL + "/byName?nodeOne=v50&nodeTwo=v2")
.accept(MediaType.APPLICATION_JSON)).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_NOT_FOUND, error.getType());
Assert.assertEquals(ErrorPlaceType.EDGE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(properties.getEdgeMsg().get("EDGE_MSG_GET_ERROR"), "v50", "v2")));
}
}

View File

@@ -0,0 +1,333 @@
package ru.resprojects.linkchecker.web.rest;
import com.google.gson.reflect.TypeToken;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import ru.resprojects.linkchecker.AppProperties;
import ru.resprojects.linkchecker.LinkcheckerApplication;
import ru.resprojects.linkchecker.TestUtils;
import ru.resprojects.linkchecker.util.exeptions.ErrorInfo;
import ru.resprojects.linkchecker.util.exeptions.ErrorPlaceType;
import ru.resprojects.linkchecker.util.exeptions.ErrorType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static ru.resprojects.linkchecker.dto.GraphDto.NodeGraph;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LinkcheckerApplication.class)
@ActiveProfiles(profiles = {"test", "debug"})
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
config = @SqlConfig(encoding = "UTF-8"))
@AutoConfigureMockMvc
public class GraphNodeRestControllerTests {
private static final Logger LOG = LoggerFactory.getLogger(GraphNodeRestControllerTests.class);
@Autowired
private MockMvc mvc;
@Autowired
private AppProperties properties;
@Test
public void getNodes() throws Exception {
this.mvc.perform(get(GraphNodeRestController.NODES_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(TestUtils.nodesGraph)));
}
@Test
public void getNodeById() throws Exception {
this.mvc.perform(get(GraphNodeRestController.NODES_REST_URL + "/byId/5000").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(TestUtils.nodeGraph)));
}
@Test
public void getNodeByName() throws Exception {
this.mvc.perform(get(GraphNodeRestController.NODES_REST_URL + "/byName/v1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(TestUtils.nodeGraph)));
}
@Test
public void getNodeByNameNotFoundException() throws Exception {
MvcResult result = this.mvc.perform(get(GraphNodeRestController.NODES_REST_URL + "/byName/v10")
.accept(MediaType.APPLICATION_JSON)).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_NOT_FOUND, error.getType());
Assert.assertEquals(ErrorPlaceType.NODE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), "v10")));
}
@Test
public void getNodeByIdNotFoundException() throws Exception {
MvcResult result = this.mvc.perform(get(GraphNodeRestController.NODES_REST_URL + "/byId/5050")
.accept(MediaType.APPLICATION_JSON)).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_NOT_FOUND, error.getType());
Assert.assertEquals(ErrorPlaceType.NODE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(properties.getAppMsg().get("MSG_BY_ID_ERROR"), ErrorPlaceType.NODE, 5050)));
}
@Test
public void addNewNodeToGraph() throws Exception {
NodeGraph newNode = new NodeGraph("v6");
MvcResult result = this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newNode))).andReturn();
Assert.assertEquals(HttpStatus.CREATED.value(), result.getResponse().getStatus());
NodeGraph returnedNode = TestUtils.mapFromJson(result.getResponse().getContentAsString(), NodeGraph.class);
Assert.assertNotNull(returnedNode);
Assert.assertEquals(newNode.getName(), returnedNode.getName());
Assert.assertNotNull(returnedNode.getId());
}
@Test
public void addNewNodesToGraph() throws Exception {
Set<NodeGraph> newNodes = Stream.of(
new NodeGraph("v6"),
new NodeGraph("v7"),
new NodeGraph("v8")
).collect(Collectors.toSet());
MvcResult result = this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newNodes))).andReturn();
Assert.assertEquals(HttpStatus.CREATED.value(), result.getResponse().getStatus());
Type listType = new TypeToken<HashSet<NodeGraph>>() {}.getType();
Set<NodeGraph> returnedNodes = TestUtils.mapFromJson(result.getResponse().getContentAsString(), listType);
Assert.assertEquals(newNodes.size(), returnedNodes.size());
Assert.assertTrue(returnedNodes.stream().anyMatch(ng -> ng.getName().equals("v6")));
}
@Test
public void addNewNodeValidationException() throws Exception {
MvcResult result = this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(new NodeGraph()))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.VALIDATION_ERROR, error.getType());
Assert.assertEquals(ErrorPlaceType.APP, error.getPlace());
LOG.info(Arrays.asList(error.getMessages()).toString());
}
@Test
public void addNewNodeAlreadyPresentException() throws Exception {
NodeGraph newNode = new NodeGraph("v1");
MvcResult result = this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newNode))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_ERROR, error.getType());
Assert.assertEquals(ErrorPlaceType.NODE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(properties.getNodeMsg().get("NODE_MSG_ALREADY_PRESENT_ERROR"), newNode.getName())));
LOG.info(errMsgs.toString());
}
@Test
public void addNewNodesEmptyCollectionException() throws Exception {
Set<NodeGraph> newNodes = Collections.emptySet();
MvcResult result = this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newNodes))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_ERROR, error.getType());
Assert.assertEquals(ErrorPlaceType.NODE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(properties.getAppMsg().get("MSG_COLLECTION_EMPTY")));
LOG.info(errMsgs.toString());
}
@Test
public void addNewNodesCollectionContainNullObjectException() throws Exception {
Set<NodeGraph> newNodes = Stream.of(
null,
new NodeGraph("v7"),
new NodeGraph("v8")
).collect(Collectors.toSet());
MvcResult result = this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newNodes))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_ERROR, error.getType());
Assert.assertEquals(ErrorPlaceType.NODE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(properties.getAppMsg().get("MSG_COLLECTION_CONTAIN_NULL")));
LOG.info(errMsgs.toString());
}
@Test
public void addNewNodesCollectionContainAlreadyPresentNodeException() throws Exception {
Set<NodeGraph> newNodes = Stream.of(
new NodeGraph("v1"),
new NodeGraph("v7"),
new NodeGraph("v8")
).collect(Collectors.toSet());
MvcResult result = this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newNodes))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_ERROR, error.getType());
Assert.assertEquals(ErrorPlaceType.NODE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(
properties.getNodeMsg().get("NODE_MSG_ALREADY_PRESENT_ERROR"),"v1")));
LOG.info(errMsgs.toString());
}
@Test
public void deleteAllNodes() throws Exception {
this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());
this.mvc.perform(get(GraphNodeRestController.NODES_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(Collections.EMPTY_SET)));
}
@Test
public void deleteNodeById() throws Exception {
Set<NodeGraph> nodes = TestUtils.nodesGraph.stream().filter(ng -> ng.getId() != 5000).collect(Collectors.toSet());
this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byId/5000").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());
this.mvc.perform(get(GraphNodeRestController.NODES_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(nodes)));
}
@Test
public void deleteNodeByName() throws Exception {
Set<NodeGraph> nodes = TestUtils.nodesGraph.stream().filter(ng -> !ng.getName().equals("v1")).collect(Collectors.toSet());
this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byName/v1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());
this.mvc.perform(get(GraphNodeRestController.NODES_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(nodes)));
}
@Test
public void deleteNodeByObject() throws Exception {
Set<NodeGraph> nodes = TestUtils.nodesGraph.stream().filter(ng -> !ng.getName().equals("v1")).collect(Collectors.toSet());
this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byObj").contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(TestUtils.nodeGraph)))
.andExpect(status().isNoContent());
this.mvc.perform(get(GraphNodeRestController.NODES_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(nodes)));
}
@Test
public void deleteNodeByIdNotFoundException() throws Exception {
MvcResult result = this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byId/5050")
.accept(MediaType.APPLICATION_JSON)).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_NOT_FOUND, error.getType());
Assert.assertEquals(ErrorPlaceType.NODE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(
properties.getAppMsg().get("MSG_BY_ID_ERROR"), ErrorPlaceType.NODE, 5050)));
LOG.info(errMsgs.toString());
}
@Test
public void deleteNodeByNameNotFoundException() throws Exception {
MvcResult result = this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byName/v10")
.accept(MediaType.APPLICATION_JSON)).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_NOT_FOUND, error.getType());
Assert.assertEquals(ErrorPlaceType.NODE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(
properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), "v10")));
LOG.info(errMsgs.toString());
}
@Test
public void deleteNodeByObjectWithNullIdNotFoundException() throws Exception {
NodeGraph newNode = new NodeGraph(null, "v10", 0);
MvcResult result = this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byObj")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newNode))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_NOT_FOUND, error.getType());
Assert.assertEquals(ErrorPlaceType.NODE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(
properties.getNodeMsg().get("NODE_MSG_BY_OBJECT_ERROR"), newNode.toString())));
LOG.info(errMsgs.toString());
}
@Test
public void deleteNodeByObjectNotFoundException() throws Exception {
NodeGraph newNode = new NodeGraph(5020, "v10", 0);
MvcResult result = this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byObj")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newNode))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_NOT_FOUND, error.getType());
Assert.assertEquals(ErrorPlaceType.NODE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(
properties.getNodeMsg().get("NODE_MSG_BY_OBJECT_ERROR"), newNode.toString())));
LOG.info(errMsgs.toString());
}
@Test
public void deleteNodeByObjectWithIncorrectIdException() throws Exception {
NodeGraph newNode = new NodeGraph(5000, "v10", 0);
MvcResult result = this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byObj")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newNode))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_NOT_FOUND, error.getType());
Assert.assertEquals(ErrorPlaceType.NODE, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(
properties.getNodeMsg().get("NODE_MSG_BY_OBJECT_ERROR"), newNode.toString())));
LOG.info(errMsgs.toString());
}
}

View File

@@ -0,0 +1,47 @@
package ru.resprojects.linkchecker.web.rest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import ru.resprojects.linkchecker.TestUtils;
import ru.resprojects.linkchecker.services.GraphService;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@RunWith(SpringRunner.class)
@WebMvcTest(GraphRestController.class)
public class GraphRestControllerMockTests {
@Autowired
private MockMvc mvc;
@MockBean
private GraphService graphService;
@Test
public void checkRouteInGraph() throws Exception {
List<String> route = Stream.of("v1", "v2", "v3").collect(Collectors.toList());
String returnedResult = String.format("Route for nodes %s is found", route.toString());
given(this.graphService.checkRoute(anySet())).willReturn(returnedResult);
MvcResult result = this.mvc.perform(post(GraphRestController.REST_URL + "/checkroute")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(route))).andReturn();
Assert.assertEquals(HttpStatus.OK.value(), result.getResponse().getStatus());
Assert.assertEquals(result.getResponse().getContentAsString(), returnedResult);
}
}

View File

@@ -0,0 +1,173 @@
package ru.resprojects.linkchecker.web.rest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import ru.resprojects.linkchecker.AppProperties;
import ru.resprojects.linkchecker.LinkcheckerApplication;
import ru.resprojects.linkchecker.TestUtils;
import ru.resprojects.linkchecker.dto.GraphDto;
import ru.resprojects.linkchecker.util.exeptions.ErrorInfo;
import ru.resprojects.linkchecker.util.exeptions.ErrorPlaceType;
import ru.resprojects.linkchecker.util.exeptions.ErrorType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static ru.resprojects.linkchecker.dto.GraphDto.NodeGraph;
import static ru.resprojects.linkchecker.dto.GraphDto.EdgeGraph;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = LinkcheckerApplication.class)
@ActiveProfiles(profiles = {"test", "debug"})
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
config = @SqlConfig(encoding = "UTF-8"))
@AutoConfigureMockMvc
public class GraphRestControllerTests {
@Autowired
private MockMvc mvc;
@Autowired
private AppProperties properties;
@Test
public void getGraph() throws Exception {
this.mvc.perform(get(GraphRestController.REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(TestUtils.graph)));
}
@Test
public void exportGraphToGraphVizFormat() throws Exception {
MvcResult result = this.mvc.perform(get(GraphRestController.REST_URL + "/export").accept(MediaType.TEXT_HTML_VALUE))
.andReturn();
Assert.assertEquals(HttpStatus.OK.value(), result.getResponse().getStatus());
String content = result.getResponse().getContentAsString();
Assert.assertFalse(content.isEmpty());
Assert.assertTrue(content.contains("strict graph G"));
}
@Test
public void createGraph() throws Exception {
Set<NodeGraph> nodesGraph = TestUtils.nodesGraph.stream()
.map(ng -> new GraphDto.NodeGraph(ng.getName()))
.collect(Collectors.toSet());
Set<EdgeGraph> edgesGraph = TestUtils.edgesGraph.stream()
.map(eg -> new EdgeGraph(eg.getNodeOne(), eg.getNodeTwo()))
.collect(Collectors.toSet());
GraphDto graph = new GraphDto(nodesGraph, edgesGraph);
MvcResult result = this.mvc.perform(post(GraphRestController.REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(graph))).andReturn();
Assert.assertEquals(HttpStatus.CREATED.value(), result.getResponse().getStatus());
GraphDto returnedGraph = TestUtils.mapFromJson(result.getResponse().getContentAsString(), GraphDto.class);
Assert.assertNotNull(returnedGraph);
Assert.assertEquals(nodesGraph.size(), returnedGraph.getNodes().size());
Assert.assertEquals(edgesGraph.size(), returnedGraph.getEdges().size());
Assert.assertNotNull(returnedGraph.getNodes().iterator().next().getId());
Assert.assertNotNull(returnedGraph.getEdges().iterator().next().getId());
}
@Test
public void createGraphEmptyNodeCollectionException() throws Exception {
Set<NodeGraph> nodesGraph = Collections.emptySet();
Set<EdgeGraph> edgesGraph = TestUtils.edgesGraph.stream()
.map(eg -> new EdgeGraph(eg.getNodeOne(), eg.getNodeTwo()))
.collect(Collectors.toSet());
GraphDto graph = new GraphDto(nodesGraph, edgesGraph);
MvcResult result = this.mvc.perform(post(GraphRestController.REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(graph))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_ERROR, error.getType());
Assert.assertEquals(ErrorPlaceType.GRAPH, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains("NODES: " + properties.getAppMsg().get("MSG_COLLECTION_EMPTY")));
}
@Test
public void deleteGraph() throws Exception {
this.mvc.perform(delete(GraphRestController.REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent());
this.mvc.perform(get(GraphRestController.REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(TestUtils.mapToJson(new GraphDto())));
}
@Test
public void getOptions() throws Exception {
MvcResult result = this.mvc.perform(options(GraphRestController.REST_URL)
.accept(MediaType.APPLICATION_JSON)).andReturn();
Assert.assertTrue(result.getResponse().containsHeader("Allow"));
Assert.assertEquals("GET,POST,DELETE,OPTIONS", result.getResponse().getHeader("Allow"));
}
@Test
public void checkRouteEmptyInputCollectionException() throws Exception {
List<String> route = new ArrayList<>();
MvcResult result = this.mvc.perform(post(GraphRestController.REST_URL + "/checkroute")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(route))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_ERROR, error.getType());
Assert.assertEquals(ErrorPlaceType.GRAPH, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(properties.getAppMsg().get("MSG_COLLECTION_EMPTY")));
}
@Test
public void checkRouteNotEnoughDataException() throws Exception {
List<String> route = Collections.singletonList("v1");
MvcResult result = this.mvc.perform(post(GraphRestController.REST_URL + "/checkroute")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(route))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_ERROR, error.getType());
Assert.assertEquals(ErrorPlaceType.GRAPH, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(properties.getAppMsg().get("MSG_COLLECTION_CONTAIN_ONE_ELEMENT")));
}
@Test
public void checkRouteNotFoundException() throws Exception {
List<String> route = Stream.of("v7", "v2", "v1").collect(Collectors.toList());
MvcResult result = this.mvc.perform(post(GraphRestController.REST_URL + "/checkroute")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(route))).andReturn();
Assert.assertEquals(HttpStatus.UNPROCESSABLE_ENTITY.value(), result.getResponse().getStatus());
ErrorInfo error = TestUtils.mapFromJson(result.getResponse().getContentAsString(), ErrorInfo.class);
Assert.assertEquals(ErrorType.DATA_NOT_FOUND, error.getType());
Assert.assertEquals(ErrorPlaceType.GRAPH, error.getPlace());
List<String> errMsgs = Arrays.asList(error.getMessages());
Assert.assertTrue(errMsgs.contains(String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), "v7")));
}
}

View File

@@ -0,0 +1,186 @@
package ru.resprojects.linkchecker.web.rest.apidocs;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import ru.resprojects.linkchecker.TestUtils;
import ru.resprojects.linkchecker.dto.GraphDto;
import ru.resprojects.linkchecker.web.rest.GraphRestController;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles(profiles = {"test"})
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
config = @SqlConfig(encoding = "UTF-8"))
@AutoConfigureMockMvc
@AutoConfigureRestDocs(outputDir = "target/generated-snippets")
public class GraphApiDocumentation {
@Autowired
private MockMvc mvc;
@Test
public void getGraph() throws Exception {
this.mvc.perform(MockMvcRequestBuilders.get(GraphRestController.REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(getGraphResponseDoc("get-graph"));
}
@Test
public void exportGraphToGraphVizFormat() throws Exception {
this.mvc.perform(get(GraphRestController.REST_URL + "/export").accept(MediaType.TEXT_HTML_VALUE))
.andExpect(status().isOk())
.andDo(document("export-graph"));
}
@Test
public void createGraph() throws Exception {
String jsonGraph = "{\n" +
" \"nodes\":[\n" +
" {\"name\":\"v1\"},\n" +
" {\"name\":\"v2\"},\n" +
" {\"name\":\"v3\"},\n" +
" {\"name\":\"v4\"},\n" +
" {\"name\":\"v5\"}\n" +
" ],\n" +
" \"edges\":[\n" +
" {\"nodeOne\":\"v1\",\"nodeTwo\":\"v2\"},\n" +
" {\"nodeOne\":\"v2\",\"nodeTwo\":\"v3\"},\n" +
" {\"nodeOne\":\"v3\",\"nodeTwo\":\"v4\"},\n" +
" {\"nodeOne\":\"v3\",\"nodeTwo\":\"v5\"},\n" +
" {\"nodeOne\":\"v5\",\"nodeTwo\":\"v4\"},\n" +
" {\"nodeOne\":\"v5\",\"nodeTwo\":\"v2\"}\n" +
" ]\n" +
"}";
this.mvc.perform(post(GraphRestController.REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(jsonGraph))
.andExpect(status().isCreated())
.andDo(document("create-graph",
requestFields(
fieldWithPath("nodes")
.description("Коллекция вершин графа (ноды)"),
fieldWithPath("nodes[].name")
.description("Уникальное имя вершины графа"),
fieldWithPath("edges")
.description("Коллекция рёбер графа"),
fieldWithPath("edges[].nodeOne")
.description("Уникальное имя вершины графа"),
fieldWithPath("edges[].nodeTwo")
.description("Уникальное имя вершины графа")
)))
.andDo(getGraphResponseDoc("create-graph"));
}
@Test
public void createGraphEmptyNodeCollectionException() throws Exception {
Set<GraphDto.NodeGraph> nodesGraph = Collections.emptySet();
Set<GraphDto.EdgeGraph> edgesGraph = TestUtils.edgesGraph.stream()
.map(eg -> new GraphDto.EdgeGraph(eg.getNodeOne(), eg.getNodeTwo()))
.collect(Collectors.toSet());
GraphDto graph = new GraphDto(nodesGraph, edgesGraph);
this.mvc.perform(post(GraphRestController.REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(graph)))
.andDo(ErrorResponseDoc("create-graph-exception"));
}
@Test
public void checkRouteEmptyInputCollectionException() throws Exception {
List<String> route = new ArrayList<>();
this.mvc.perform(post(GraphRestController.REST_URL + "/checkroute")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(route)))
.andDo(ErrorResponseDoc( "checkroute-graph-exception-1"));
}
@Test
public void checkRouteNotEnoughDataException() throws Exception {
List<String> route = Collections.singletonList("v1");
this.mvc.perform(post(GraphRestController.REST_URL + "/checkroute")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(route)))
.andDo(ErrorResponseDoc( "checkroute-graph-exception-2"));
}
@Test
public void checkRouteNotFoundException() throws Exception {
List<String> route = Stream.of("v7", "v2", "v1").collect(Collectors.toList());
this.mvc.perform(post(GraphRestController.REST_URL + "/checkroute")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(route)))
.andDo(ErrorResponseDoc( "checkroute-graph-exception-3"));
}
@Test
public void deleteGraph() throws Exception {
this.mvc.perform(delete(GraphRestController.REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent())
.andDo(document("delete-graph"));
}
private RestDocumentationResultHandler getGraphResponseDoc(String documentIdentifier) {
return document(documentIdentifier,
responseFields(
fieldWithPath("nodes")
.description("Коллекция вершин графа (ноды)"),
fieldWithPath("nodes[].id")
.description("Идентификатор вершины графа"),
fieldWithPath("nodes[].name")
.description("Уникальное имя вершины графа"),
fieldWithPath("nodes[].counter")
.description("Колличество проходов через узел"),
fieldWithPath("edges")
.description("Коллекция рёбер графа"),
fieldWithPath("edges[].id")
.description("Уникальный идентификатор ребра графа"),
fieldWithPath("edges[].nodeOne")
.description("Уникальное имя вершины графа"),
fieldWithPath("edges[].nodeTwo")
.description("Уникальное имя вершины графа")
));
}
public static RestDocumentationResultHandler ErrorResponseDoc(String documentIdentifier) {
return document(documentIdentifier,
responseFields(
fieldWithPath("url")
.description("REST-запрос, при котором возникла ошибка"),
fieldWithPath("type")
.description("тип ошибки"),
fieldWithPath("place")
.description("места возникновения ошибок"),
fieldWithPath("messages")
.description("сообщения об ошибках")
));
}
}

View File

@@ -0,0 +1,50 @@
package ru.resprojects.linkchecker.web.rest.apidocs;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import ru.resprojects.linkchecker.TestUtils;
import ru.resprojects.linkchecker.services.GraphService;
import ru.resprojects.linkchecker.web.rest.GraphRestController;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@WebMvcTest(GraphRestController.class)
@AutoConfigureRestDocs(outputDir = "target/generated-snippets")
public class GraphCheckRouteApiDocumentation {
@Autowired
private MockMvc mvc;
@MockBean
private GraphService graphService;
@Test
public void checkRouteInGraph() throws Exception {
List<String> route = Stream.of("v1", "v2", "v3").collect(Collectors.toList());
String returnedResult = String.format("Route for nodes %s is found", route.toString());
given(this.graphService.checkRoute(anySet())).willReturn(returnedResult);
this.mvc.perform(post(GraphRestController.REST_URL + "/checkroute")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(route)))
.andExpect(status().isOk())
.andDo(document("checkroute-graph"))
;
}
}

View File

@@ -0,0 +1,237 @@
package ru.resprojects.linkchecker.web.rest.apidocs;
import com.google.gson.reflect.TypeToken;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import ru.resprojects.linkchecker.web.rest.GraphEdgeRestController;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles(profiles = {"test"})
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
config = @SqlConfig(encoding = "UTF-8"))
@AutoConfigureMockMvc
@AutoConfigureRestDocs(outputDir = "target/generated-snippets")
public class GraphEdgeApiDocumentation {
@Autowired
private MockMvc mvc;
@Test
public void getEdges() throws Exception {
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(ResponseEdgesDoc("get-edges"));
}
@Test
public void getEdgeById() throws Exception {
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL + "/byId/5005").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(ResponseEdgeDoc("get-edge-by-id"));
}
@Test
public void getEdgesByNodeName() throws Exception {
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL + "/byName/v1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(ResponseEdgesDoc("get-edges-by-node-name"));
}
@Test
public void getEdgeByNodeNames() throws Exception {
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL + "/byName?nodeOne=v1&nodeTwo=v2").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(ResponseEdgeDoc("get-edge-by-nodes-name"));
}
@Test
public void getEdgeByIdNotFoundException() throws Exception {
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL + "/byId/5050")
.accept(MediaType.APPLICATION_JSON))
.andDo(GraphApiDocumentation.ErrorResponseDoc("get-edge-exception-1"));
}
@Test
public void getEdgesByNameNotFoundException() throws Exception {
this.mvc.perform(get(GraphEdgeRestController.EDGE_REST_URL + "/byName/v100")
.accept(MediaType.APPLICATION_JSON))
.andDo(GraphApiDocumentation.ErrorResponseDoc("get-edge-exception-2"));
}
@Test
public void addNewEdge() throws Exception {
String jsonEdge = "{\"nodeOne\": \"v1\", \"nodeTwo\": \"v4\"}";
this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(jsonEdge))
.andExpect(status().isCreated())
.andDo(document("create-edge",
requestFields(
fieldWithPath("nodeOne")
.description("Уникальное имя вершины графа"),
fieldWithPath("nodeTwo")
.description("Уникальное имя вершины графа")
)))
.andDo(ResponseEdgeDoc("create-edge"));
}
@Test
public void addNewEdges() throws Exception {
String jsonEdge = "[{\"nodeOne\": \"v1\", \"nodeTwo\": \"v4\"},{\"nodeOne\": \"v2\", \"nodeTwo\": \"v4\"}]";
this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(jsonEdge))
.andExpect(status().isCreated())
.andDo(document("create-edges",
requestFields(
fieldWithPath("[]")
.description("Коллекция рёбер графа"),
fieldWithPath("[].nodeOne")
.description("Уникальное имя вершины графа"),
fieldWithPath("[].nodeTwo")
.description("Уникальное имя вершины графа")
)))
.andDo(ResponseEdgesDoc("create-edges"));
}
@Test
public void addNewEdgeValidationException() throws Exception {
this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content("{\"nodeOne\": \"\", \"nodeTwo\": \"\"}"))
.andDo(GraphApiDocumentation.ErrorResponseDoc("create-edge-exception-1"));
}
@Test
public void addNewEdgeAlreadyPresentException() throws Exception {
this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content("{\"nodeOne\": \"v1\", \"nodeTwo\": \"v2\"}"))
.andDo(GraphApiDocumentation.ErrorResponseDoc("create-edge-exception-2"));
}
@Test
public void addNewEdgesEmptyCollectionException() throws Exception {
this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content("[]"))
.andDo(GraphApiDocumentation.ErrorResponseDoc("create-edge-exception-3"));
}
@Test
public void addNewEdgesCollectionContainNullObjectException() throws Exception {
String jsonEdge = "[null,{\"nodeOne\": \"v2\", \"nodeTwo\": \"v4\"}]";
this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(jsonEdge))
.andDo(GraphApiDocumentation.ErrorResponseDoc("create-edge-exception-4"));
}
@Test
public void addNewEdgesCollectionContainAlreadyPresentNodeException() throws Exception {
String jsonEdge = "[{\"nodeOne\": \"v1\", \"nodeTwo\": \"v2\"},{\"nodeOne\": \"v1\", \"nodeTwo\": \"v4\"},{\"nodeOne\": \"v2\", \"nodeTwo\": \"v4\"}]";
this.mvc.perform(post(GraphEdgeRestController.EDGE_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(jsonEdge))
.andDo(GraphApiDocumentation.ErrorResponseDoc("create-edge-exception-5"));
}
@Test
public void deleteAllEdges() throws Exception {
this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent())
.andDo(document("delete-all-edges"));
}
@Test
public void deleteEdgeById() throws Exception {
this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL + "/byId/5005").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent())
.andDo(document("delete-edge-by-id"));
}
@Test
public void deleteEdgesByNodeName() throws Exception {
this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL + "/byName/v4").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent())
.andDo(document("delete-edges-by-node-name"));
}
@Test
public void deleteEdgeByNodeNames() throws Exception {
this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL + "/byName?nodeOne=v1&nodeTwo=v2").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent())
.andDo(document("delete-edge-by-nodes-name"));
}
@Test
public void deleteEdgeByIdNotFoundException() throws Exception {
this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL + "/byId/5050")
.accept(MediaType.APPLICATION_JSON))
.andDo(GraphApiDocumentation.ErrorResponseDoc("delete-edge-exception-1"));
}
@Test
public void deleteEdgesByNodeNameNotFoundException() throws Exception {
this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL + "/byName/v50")
.accept(MediaType.APPLICATION_JSON))
.andDo(GraphApiDocumentation.ErrorResponseDoc("delete-edge-exception-2"));
}
@Test
public void deleteEdgeByNodeNamesNotFoundException() throws Exception {
this.mvc.perform(delete(GraphEdgeRestController.EDGE_REST_URL + "/byName?nodeOne=v50&nodeTwo=v2")
.accept(MediaType.APPLICATION_JSON))
.andDo(GraphApiDocumentation.ErrorResponseDoc("delete-edge-exception-3"));
}
private static RestDocumentationResultHandler ResponseEdgeDoc(String documentIdentifier) {
return document(documentIdentifier,
responseFields(
fieldWithPath("id")
.description("Идентификатор ребра графа"),
fieldWithPath("nodeOne")
.description("Уникальное имя первой вершины графа, которое связывается текущим ребром"),
fieldWithPath("nodeTwo")
.description("Уникальное имя второй вершины графа, которое связывается текущим ребром")
));
}
private static RestDocumentationResultHandler ResponseEdgesDoc(String documentIdentifier) {
return document(documentIdentifier,
responseFields(
fieldWithPath("[]")
.description("Коллекция рёбер"),
fieldWithPath("[].id")
.description("Идентификатор ребра графа"),
fieldWithPath("[].nodeOne")
.description("Уникальное имя первой вершины графа, которое связывается текущим ребром"),
fieldWithPath("[].nodeTwo")
.description("Уникальное имя второй вершины графа, которое связывается текущим ребром")
));
}
}

View File

@@ -0,0 +1,242 @@
package ru.resprojects.linkchecker.web.rest.apidocs;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import ru.resprojects.linkchecker.TestUtils;
import ru.resprojects.linkchecker.dto.GraphDto;
import ru.resprojects.linkchecker.web.rest.GraphNodeRestController;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles(profiles = {"test"})
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
scripts = {"classpath:schema-h2.sql", "classpath:data-h2.sql"},
config = @SqlConfig(encoding = "UTF-8"))
@AutoConfigureMockMvc
@AutoConfigureRestDocs(outputDir = "target/generated-snippets")
public class GraphNodeApiDocumentation {
@Autowired
private MockMvc mvc;
@Test
public void getNodes() throws Exception {
this.mvc.perform(get(GraphNodeRestController.NODES_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(ResponseNodesDoc("get-nodes"));
}
@Test
public void getNodeById() throws Exception {
this.mvc.perform(get(GraphNodeRestController.NODES_REST_URL + "/byId/5000").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(ResponseNodeDoc("get-node-by-id"));
}
@Test
public void getNodeByName() throws Exception {
this.mvc.perform(get(GraphNodeRestController.NODES_REST_URL + "/byName/v1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(ResponseNodeDoc("get-node-by-name"));
}
@Test
public void getNodeByNameNotFoundException() throws Exception {
this.mvc.perform(get(GraphNodeRestController.NODES_REST_URL + "/byName/v10")
.accept(MediaType.APPLICATION_JSON))
.andDo(GraphApiDocumentation.ErrorResponseDoc("get-node-by-name-exception"));
}
@Test
public void addNewNodeToGraph() throws Exception {
String jsonNode = "{\"name\": \"v6\"}";
this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(jsonNode))
.andExpect(status().isCreated())
.andDo(document("create-node",
requestFields(
fieldWithPath("name")
.description("Уникальное имя вершины графа")
)))
.andDo(ResponseNodeDoc("create-node"));
}
@Test
public void addNewNodesToGraph() throws Exception {
String jsonNodes = "[{\"name\":\"v6\"}, {\"name\":\"v7\"}, {\"name\":\"v8\"}]";
this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(jsonNodes))
.andDo(document("create-nodes",
requestFields(
fieldWithPath("[]")
.description("Коллекция вершин графа (ноды)"),
fieldWithPath("[].name")
.description("Уникальное имя вершины графа")
)))
.andDo(ResponseNodesDoc("create-nodes"))
;
}
@Test
public void addNewNodeValidationException() throws Exception {
String jsonNode = "{\"name\":\"\"}";
this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(jsonNode))
.andDo(GraphApiDocumentation.ErrorResponseDoc("create-node-exception-1"));
}
@Test
public void addNewNodeAlreadyPresentException() throws Exception {
String jsonNode = "{\"name\": \"v1\"}";
this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(jsonNode))
.andDo(GraphApiDocumentation.ErrorResponseDoc("create-node-exception-2"));
}
@Test
public void addNewNodesEmptyCollectionException() throws Exception {
this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content("[]"))
.andDo(GraphApiDocumentation.ErrorResponseDoc("create-node-exception-3"));
}
@Test
public void addNewNodesCollectionContainNullObjectException() throws Exception {
String jsonNodes = "[null, {\"name\": \"v7\"}, {\"name\": \"v8\"}]";
this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(jsonNodes))
.andDo(GraphApiDocumentation.ErrorResponseDoc("create-node-exception-4"));
}
@Test
public void addNewNodesCollectionContainAlreadyPresentNodeException() throws Exception {
String jsonNodes = "[{\"name\":\"v1\"}, {\"name\":\"v7\"}, {\"name\":\"v8\"}]";
this.mvc.perform(post(GraphNodeRestController.NODES_REST_URL + "/create/byBatch")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(jsonNodes))
.andDo(GraphApiDocumentation.ErrorResponseDoc("create-node-exception-5"));
}
@Test
public void deleteAllNodes() throws Exception {
this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent())
.andDo(document("delete-all-nodes"));
}
@Test
public void deleteNodeById() throws Exception {
this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byId/5000").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent())
.andDo(document("delete-node-by-id"));
}
@Test
public void deleteNodeByName() throws Exception {
this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byName/v1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNoContent())
.andDo(document("delete-node-by-name"));
}
@Test
public void deleteNodeByObject() throws Exception {
this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byObj").contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(TestUtils.nodeGraph)))
.andExpect(status().isNoContent())
.andDo(document("delete-node-by-obj"));
}
@Test
public void deleteNodeByIdNotFoundException() throws Exception {
this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byId/5050")
.accept(MediaType.APPLICATION_JSON))
.andDo(GraphApiDocumentation.ErrorResponseDoc("delete-node-exception-1"));
}
@Test
public void deleteNodeByNameNotFoundException() throws Exception {
this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byName/v10")
.accept(MediaType.APPLICATION_JSON))
.andDo(GraphApiDocumentation.ErrorResponseDoc("delete-node-exception-2"));
}
@Test
public void deleteNodeByObjectWithNullIdNotFoundException() throws Exception {
GraphDto.NodeGraph newNode = new GraphDto.NodeGraph(null, "v10", 0);
this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byObj")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newNode)))
.andDo(GraphApiDocumentation.ErrorResponseDoc("delete-node-exception-3"));
}
@Test
public void deleteNodeByObjectNotFoundException() throws Exception {
GraphDto.NodeGraph newNode = new GraphDto.NodeGraph(5020, "v10", 0);
this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byObj")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newNode)))
.andDo(GraphApiDocumentation.ErrorResponseDoc("delete-node-exception-4"));
}
@Test
public void deleteNodeByObjectWithIncorrectIdException() throws Exception {
GraphDto.NodeGraph newNode = new GraphDto.NodeGraph(5000, "v10", 0);
this.mvc.perform(delete(GraphNodeRestController.NODES_REST_URL + "/byObj")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(TestUtils.mapToJson(newNode)))
.andDo(GraphApiDocumentation.ErrorResponseDoc("delete-node-exception-5"));
}
private static RestDocumentationResultHandler ResponseNodeDoc(String documentIdentifier) {
return document(documentIdentifier,
responseFields(
fieldWithPath("id")
.description("Идентификатор вершины графа"),
fieldWithPath("name")
.description("Уникальное имя вершины графа"),
fieldWithPath("counter")
.description("Колличество проходов через вершину графа")
));
}
private static RestDocumentationResultHandler ResponseNodesDoc(String documentIdentifier) {
return document(documentIdentifier,
responseFields(
fieldWithPath("[]")
.description("Коллекция вершин графа (ноды)"),
fieldWithPath("[].id")
.description("Идентификатор вершины графа"),
fieldWithPath("[].name")
.description("Уникальное имя вершины графа"),
fieldWithPath("[].counter")
.description("Колличество проходов через вершину графа")
));
}
}

View File

@@ -0,0 +1,99 @@
package ru.resprojects.linkchecker.web.rest.json;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.GsonTester;
import org.springframework.test.context.junit4.SpringRunner;
import ru.resprojects.linkchecker.dto.GraphDto;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static ru.resprojects.linkchecker.TestUtils.*;
@RunWith(SpringRunner.class)
@JsonTest
public class JsonGraphDtoTests {
@Autowired
private GsonTester<GraphDto> jsonGraph;
@Autowired
private GsonTester<GraphDto.NodeGraph> jsonNodeGraph;
@Autowired
private GsonTester<GraphDto.EdgeGraph> jsonEdgeGraph;
@Autowired
private GsonTester<Set<GraphDto.NodeGraph>> jsonNodesGraph;
@Autowired
private GsonTester<Set<GraphDto.EdgeGraph>> jsonEdgesGraph;
@Test
public void serializeJsonGraphDto() throws Exception {
assertThat(this.jsonGraph.write(graph)).isEqualTo("graph.json");
assertThat(this.jsonGraph.write(graph)).isEqualToJson("graph.json");
assertThat(this.jsonGraph.write(graph)).hasJsonPathArrayValue("@.nodes");
assertThat(this.jsonGraph.write(graph)).hasJsonPathArrayValue("@.edges");
assertThat(this.jsonGraph.write(graph))
.extractingJsonPathArrayValue("@.nodes")
.hasSameSizeAs(graph.getNodes());
assertThat(this.jsonGraph.write(graph))
.extractingJsonPathArrayValue("@.edges")
.hasSameSizeAs(graph.getEdges());
}
@Test
public void serializeJsonNodes() throws Exception {
assertThat(this.jsonNodesGraph.write(nodesGraph)).isEqualTo("nodes.json");
assertThat(this.jsonNodesGraph.write(nodesGraph)).isEqualToJson("nodes.json");
}
@Test
public void serializeJsonEdges() throws Exception {
assertThat(this.jsonEdgesGraph.write(edgesGraph)).isEqualTo("edges.json");
assertThat(this.jsonEdgesGraph.write(edgesGraph)).isEqualToJson("edges.json");
}
@Test
public void serializeJsonNode() throws Exception {
assertThat(this.jsonNodeGraph.write(nodeGraph)).isEqualTo("node.json");
assertThat(this.jsonNodeGraph.write(nodeGraph)).isEqualToJson("node.json");
assertThat(this.jsonNodeGraph.write(nodeGraph)).extractingJsonPathStringValue("@.name")
.isEqualTo(nodeGraph.getName());
assertThat(this.jsonNodeGraph.write(nodeGraph))
.extractingJsonPathNumberValue("@.id")
.isEqualTo(nodeGraph.getId());
}
@Test
public void serializeJsonEdge() throws Exception {
assertThat(this.jsonEdgeGraph.write(edgeGraph)).isEqualTo("edge.json");
assertThat(this.jsonEdgeGraph.write(edgeGraph)).isEqualToJson("edge.json");
assertThat(this.jsonEdgeGraph.write(edgeGraph)).extractingJsonPathStringValue("@.nodeOne")
.isEqualTo(edgeGraph.getNodeOne());
assertThat(this.jsonEdgeGraph.write(edgeGraph))
.extractingJsonPathStringValue("@.nodeTwo")
.isEqualTo(edgeGraph.getNodeTwo());
assertThat(this.jsonEdgeGraph.write(edgeGraph))
.extractingJsonPathNumberValue("@.id")
.isEqualTo(edgeGraph.getId());
}
@Test
public void deserializeJsonNode() throws Exception {
String content = "{\"id\": 5000, \"name\": \"v1\", \"counter\": 0}";
assertThat(this.jsonNodeGraph.parse(content)).isEqualTo(nodeGraph);
}
@Test
public void deserializeJsonEdge() throws Exception {
String content = "{\"id\": 5005, \"nodeOne\": \"v1\", \"nodeTwo\": \"v2\"}";
assertThat(this.jsonEdgeGraph.parse(content)).isEqualTo(edgeGraph);
}
}

View File

@@ -0,0 +1 @@
{"id": 5005, "nodeOne": "v1", "nodeTwo": "v2"}

View File

@@ -0,0 +1,22 @@
[
{
"id": 5006,
"nodeOne": "v1",
"nodeTwo": "v3"
},
{
"id": 5005,
"nodeOne": "v1",
"nodeTwo": "v2"
},
{
"id": 5007,
"nodeOne": "v1",
"nodeTwo": "v5"
},
{
"id": 5008,
"nodeOne": "v3",
"nodeTwo": "v4"
}
]

View File

@@ -0,0 +1,51 @@
{
"nodes": [
{
"id": 5000,
"name": "v1",
"counter": 0
},
{
"id": 5001,
"name": "v2",
"counter": 0
},
{
"id": 5002,
"name": "v3",
"counter": 0
},
{
"id": 5003,
"name": "v4",
"counter": 0
},
{
"id": 5004,
"name": "v5",
"counter": 0
}
],
"edges": [
{
"id": 5006,
"nodeOne": "v1",
"nodeTwo": "v3"
},
{
"id": 5005,
"nodeOne": "v1",
"nodeTwo": "v2"
},
{
"id": 5007,
"nodeOne": "v1",
"nodeTwo": "v5"
},
{
"id": 5008,
"nodeOne": "v3",
"nodeTwo": "v4"
}
]
}

View File

@@ -0,0 +1 @@
{"id": 5000, "name": "v1", "counter": 0}

Some files were not shown because too many files have changed in this diff Show More