mirror of
https://github.com/valitydev/beholder.git
synced 2024-11-06 00:35:19 +00:00
OPS-68: Implementation (#4)
* Implementation * Readme + fixes * Tests * 🔄 Synced file(s) with valitydev/configurations (#3) * 🔄 Created local '.github/workflows/basic-linters.yml' from remote 'workflows/base/basic-linters.yml' * 🔄 Created local 'LICENSE' from remote 'LICENSE' * 🔄 Created local '.github/settings.yml' from remote '.github/settings.yml' Co-authored-by: Egor Cherniak <cherniak3@yandex.ru> * Add renovate * Feedback edits Co-authored-by: Vality Bot <bots@vality.dev>
This commit is contained in:
parent
2b25f77bd6
commit
1d169a7577
2
.github/settings.yml
vendored
Normal file
2
.github/settings.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# These settings are synced to GitHub by https://probot.github.io/apps/settings/
|
||||||
|
_extends: .github
|
10
.github/workflows/basic-linters.yml
vendored
Normal file
10
.github/workflows/basic-linters.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name: Vality basic linters
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
uses: valitydev/base-workflows/.github/workflows/basic-linters.yml@v1
|
10
.github/workflows/build.yml
vendored
Normal file
10
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name: Build Maven Artifact
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
uses: valitydev/java-workflow/.github/workflows/maven-service-build.yml@v1
|
13
.github/workflows/deploy.yml
vendored
Normal file
13
.github/workflows/deploy.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
name: Maven Deploy Artifact
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
uses: valitydev/java-workflow/.github/workflows/maven-service-deploy.yml@v1
|
||||||
|
secrets:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
mm-webhook-url: ${{ secrets.MATTERMOST_WEBHOOK_URL }}
|
2
.github/workflows/settings.yml
vendored
Normal file
2
.github/workflows/settings.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# These settings are synced to GitHub by https://probot.github.io/apps/settings/
|
||||||
|
_extends: .github
|
79
.gitignore
vendored
Normal file
79
.gitignore
vendored
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
.eunit
|
||||||
|
deps
|
||||||
|
*.o
|
||||||
|
*.beam
|
||||||
|
*.plt
|
||||||
|
erl_crash.dump
|
||||||
|
ebin/*.beam
|
||||||
|
rel/example_project
|
||||||
|
.concrete/DEV_MODE
|
||||||
|
.rebar
|
||||||
|
target/
|
||||||
|
pom.xml.tag
|
||||||
|
pom.xml.releaseBackup
|
||||||
|
pom.xml.versionsBackup
|
||||||
|
pom.xml.next
|
||||||
|
release.properties
|
||||||
|
dependency-reduced-pom.xml
|
||||||
|
buildNumber.properties
|
||||||
|
.mvn/timing.properties
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff:
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/vcs.xml
|
||||||
|
.idea/jsLibraryMappings.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files:
|
||||||
|
.idea/dataSources.ids
|
||||||
|
.idea/dataSources.xml
|
||||||
|
.idea/dataSources.local.xml
|
||||||
|
.idea/sqlDataSources.xml
|
||||||
|
.idea/dynamic.xml
|
||||||
|
.idea/uiDesigner.xml
|
||||||
|
|
||||||
|
# Gradle:
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/libraries
|
||||||
|
|
||||||
|
# Mongo Explorer plugin:
|
||||||
|
.idea/mongoSettings.xml
|
||||||
|
|
||||||
|
*.iws
|
||||||
|
*.ipr
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Mobile Tools for Java (J2ME)
|
||||||
|
.mtj.tmp/
|
||||||
|
|
||||||
|
# Package Files #
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
|
||||||
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
|
hs_err_pid*
|
||||||
|
env.list
|
176
LICENSE
Normal file
176
LICENSE
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
70
README.md
70
README.md
@ -1 +1,71 @@
|
|||||||
# beholder
|
# beholder
|
||||||
|
|
||||||
|
Сервис собирает метрики о производительности загрузки платёжной формы из разных регионов.
|
||||||
|
|
||||||
|
## Особенности имплементации
|
||||||
|
|
||||||
|
Точка входа в приложение: ```dev.vality.beholder.service.BeholderService.behold```. Этот метод вызывается по расписанию, указанном в свойстве ```schedule.cron```.
|
||||||
|
|
||||||
|
### Подготовка данных для загрузки платёжной формы
|
||||||
|
|
||||||
|
Для загрузки платёжной формы необходимы ```InvoiceId``` и ```InvoiceAccessToken```.
|
||||||
|
|
||||||
|
Схема взаимодействия с [swag-payments](https://github.com/valitydev/swag-payments):
|
||||||
|
|
||||||
|
![PaymentsImage](img/payments.drawio.svg)
|
||||||
|
|
||||||
|
Алгоритм взаимодействия реализован здесь: ```dev.vality.beholder.service.PaymentsService.prepareFormData```
|
||||||
|
|
||||||
|
### Загрузка платёжной формы
|
||||||
|
|
||||||
|
Beholder умеет работать c простым selenium-hub и с [lambdatest](https://www.lambdatest.com/).
|
||||||
|
В реальности обе интеграции работают через selenium API и являются совместимыми.
|
||||||
|
|
||||||
|
Алгоритм загрузки и сбора метрик формы реализован здесь: ```dev.vality.beholder.service.SeleniumService.executePaymentRequest```
|
||||||
|
|
||||||
|
Его можно разбить на следующие шаги:
|
||||||
|
|
||||||
|
1. Установить подключение с selenium-hub/lambdatest
|
||||||
|
2. Отправить запрос на загрузку формы
|
||||||
|
3. Собрать метрики загрузки формы посредством javascript'а (```dev.vality.beholder.util.SeleniumUtil.PERFORMANCE_SCRIPT```)
|
||||||
|
4. Заполнить форму и отправить запрос на проведение платежа
|
||||||
|
5. Собрать логи производительности браузера
|
||||||
|
|
||||||
|
### Обновление метрик
|
||||||
|
|
||||||
|
Собранная на предыдущем шаге информация о производительности формы записывается в соответствующие метрики prometheus'а.
|
||||||
|
Этот функционал реализован в классе ```dev.vality.beholder.service.MetricsService```
|
||||||
|
|
||||||
|
#### Метрики
|
||||||
|
|
||||||
|
| Название | Лейблы | Описание |
|
||||||
|
|--------------------------------------------------|-----------------|------------------------------------------------------------------------------|
|
||||||
|
| beholder_form_loading_requests_total | browser, region | счетчик запросов на загрузку формы |
|
||||||
|
| beholder_form_loading_failed_total | browser, region | счетчик неудачных загрузок формы |
|
||||||
|
| beholder_form_dom_complete_duration_millis | browser, region | время от момента отправки запроса до полной загрузки формы в миллисекундах |
|
||||||
|
| beholder_form_waiting_response_duration_millis | browser, region | время от момента отправки запроса до начала получения ответа в миллисекундах |
|
||||||
|
| beholder_form_receiving_response_duration_millis | browser, region | время между получением первым и последним байтом информации в миллисекундах |
|
||||||
|
| beholder_form_resource_loading_duration_millis | browser, region | время, затраченное на загрузку ресурса (включая блокировки, ожидание и т.д) |
|
||||||
|
|
||||||
|
## Тестирование
|
||||||
|
|
||||||
|
Поскольку загружать во время юнит-тестирования реальную платежную форму не представляется возможным,
|
||||||
|
реализован интеграционный тест, который отключен по умолчанию, однако может использоваться для локальной отладки сервиса.
|
||||||
|
|
||||||
|
Тест: ```dev.vality.beholder.IntegrationTest```
|
||||||
|
Подготовка к запуску теста:
|
||||||
|
|
||||||
|
1. Прописать валидные значения в следующих свойствах:
|
||||||
|
1. payments.api-url - адрес для обращения к api
|
||||||
|
2. payments.form-url - адрес для загрузки платёжной формы
|
||||||
|
3. payments.request.shop-id - идентификатор магазина, который нужно использовать
|
||||||
|
2. Прописать в свойстве ```dev.vality.beholder.IntegrationTest.TEST_USER_TOKEN``` валидный токен
|
||||||
|
3. Готово, можно запускать тест.
|
||||||
|
|
||||||
|
## Полезные ссылки
|
||||||
|
|
||||||
|
[Описание метрик производительности](https://developer.mozilla.org/en-US/docs/Web/Performance/Navigation_and_resource_timings), которые можно получить через JS.
|
||||||
|
|
||||||
|
[Описание метрик сети](https://chromedevtools.github.io/devtools-protocol/tot/Network/), которые можно получить от chromium'а.
|
||||||
|
|
||||||
|
[Регионы](https://www.lambdatest.com/capabilities-generator/), доступные для тестирования. На их основе заполнен справочник ```regions.json```
|
||||||
|
9
img/payments.drawio.svg
Normal file
9
img/payments.drawio.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
204
pom.xml
Normal file
204
pom.xml
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>dev.vality</groupId>
|
||||||
|
<artifactId>service-parent-pom</artifactId>
|
||||||
|
<version>1.0.16</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>beholder</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>beholder</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
<java.version>15</java.version>
|
||||||
|
<server.port>8022</server.port>
|
||||||
|
<management.port>8023</management.port>
|
||||||
|
<exposed.ports>${server.port} ${management.port}</exposed.ports>
|
||||||
|
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!--vality-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>dev.vality.woody</groupId>
|
||||||
|
<artifactId>woody-thrift</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>dev.vality</groupId>
|
||||||
|
<artifactId>shared-resources</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>dev.vality</groupId>
|
||||||
|
<artifactId>swag-payments</artifactId>
|
||||||
|
<version>1.627-0089567-client</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--spring-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.hibernate</groupId>
|
||||||
|
<artifactId>hibernate-validator</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--third party-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micrometer</groupId>
|
||||||
|
<artifactId>micrometer-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micrometer</groupId>
|
||||||
|
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
|
<artifactId>selenium-java</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-admin-client</artifactId>
|
||||||
|
<version>15.1.1</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-client</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-multipart-provider</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-jackson2-provider</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-jaxb-provider</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--test-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>testcontainers</artifactId>
|
||||||
|
<version>1.17.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>1.17.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.oshi</groupId>
|
||||||
|
<artifactId>oshi-core-java11</artifactId>
|
||||||
|
<version>6.1.6</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
|
||||||
|
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>${project.build.directory}/maven-shared-archive-resources</directory>
|
||||||
|
<targetPath>${project.build.directory}</targetPath>
|
||||||
|
<includes>
|
||||||
|
<include>Dockerfile</include>
|
||||||
|
</includes>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>${project.build.directory}/maven-shared-archive-resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
<excludes>
|
||||||
|
<exclude>Dockerfile</exclude>
|
||||||
|
</excludes>
|
||||||
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-remote-resources-plugin</artifactId>
|
||||||
|
<version>1.7.0</version>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.shared</groupId>
|
||||||
|
<artifactId>maven-filtering</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<configuration>
|
||||||
|
<resourceBundles>
|
||||||
|
<resourceBundle>dev.vality:shared-resources:${shared-resources.version}</resourceBundle>
|
||||||
|
</resourceBundles>
|
||||||
|
<attachToMain>false</attachToMain>
|
||||||
|
<attachToTest>false</attachToTest>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>process</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
4
renovate.json
Normal file
4
renovate.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": ["local>valitydev/.github:renovate-config"]
|
||||||
|
}
|
17
src/main/java/dev/vality/beholder/BeholderApplication.java
Normal file
17
src/main/java/dev/vality/beholder/BeholderApplication.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package dev.vality.beholder;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
@ServletComponentScan
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableScheduling
|
||||||
|
public class BeholderApplication extends SpringApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(BeholderApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
73
src/main/java/dev/vality/beholder/config/PaymentsConfig.java
Normal file
73
src/main/java/dev/vality/beholder/config/PaymentsConfig.java
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package dev.vality.beholder.config;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import dev.vality.beholder.config.properties.PaymentsProperties;
|
||||||
|
import dev.vality.swag.payments.ApiClient;
|
||||||
|
import dev.vality.swag.payments.api.ClaimsApi;
|
||||||
|
import dev.vality.swag.payments.api.InvoicesApi;
|
||||||
|
import dev.vality.swag.payments.api.PartiesApi;
|
||||||
|
import dev.vality.swag.payments.api.ShopsApi;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||||
|
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||||
|
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||||
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class PaymentsConfig {
|
||||||
|
|
||||||
|
private static final String BEARER_TYPE = "Bearer";
|
||||||
|
private static final String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ClientHttpRequestFactory clientHttpRequestFactory(PaymentsProperties paymentsProperties) {
|
||||||
|
var requestFactory = new SimpleClientHttpRequestFactory();
|
||||||
|
requestFactory.setConnectTimeout(paymentsProperties.getApiTimeoutSec().intValue() * 1000);
|
||||||
|
return requestFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
|
||||||
|
var restTemplate = new RestTemplate(clientHttpRequestFactory);
|
||||||
|
restTemplate.getMessageConverters()
|
||||||
|
.removeIf(m -> m.getClass().getName().equals(MappingJackson2HttpMessageConverter.class.getName()));
|
||||||
|
Jackson2ObjectMapperBuilder builder =
|
||||||
|
new Jackson2ObjectMapperBuilder()
|
||||||
|
.serializationInclusion(JsonInclude.Include.NON_NULL)
|
||||||
|
.dateFormat(new SimpleDateFormat(DATE_TIME_PATTERN));
|
||||||
|
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter(builder.build()));
|
||||||
|
return restTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ApiClient apiClient(RestTemplate restTemplate, PaymentsProperties paymentsProperties) {
|
||||||
|
ApiClient apiClient = new ApiClient(restTemplate);
|
||||||
|
apiClient.setBasePath(paymentsProperties.getApiUrl());
|
||||||
|
apiClient.setApiKeyPrefix(BEARER_TYPE);
|
||||||
|
return apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PartiesApi partiesApi(ApiClient apiClient) {
|
||||||
|
return new PartiesApi(apiClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ClaimsApi claimsApi(ApiClient apiClient) {
|
||||||
|
return new ClaimsApi(apiClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ShopsApi shopsApi(ApiClient apiClient) {
|
||||||
|
return new ShopsApi(apiClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public InvoicesApi invoicesApi(ApiClient apiClient) {
|
||||||
|
return new InvoicesApi(apiClient);
|
||||||
|
}
|
||||||
|
}
|
68
src/main/java/dev/vality/beholder/config/SeleniumConfig.java
Normal file
68
src/main/java/dev/vality/beholder/config/SeleniumConfig.java
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package dev.vality.beholder.config;
|
||||||
|
|
||||||
|
import dev.vality.beholder.config.properties.PaymentsProperties;
|
||||||
|
import dev.vality.beholder.config.properties.SeleniumProperties;
|
||||||
|
import dev.vality.beholder.converter.LogEntriesToNetworkLogsConverter;
|
||||||
|
import dev.vality.beholder.model.Region;
|
||||||
|
import dev.vality.beholder.service.SeleniumService;
|
||||||
|
import dev.vality.beholder.util.FileUtil;
|
||||||
|
import dev.vality.beholder.util.SeleniumUtil;
|
||||||
|
import org.openqa.selenium.remote.DesiredCapabilities;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class SeleniumConfig {
|
||||||
|
|
||||||
|
@Value("${dictionary.regions}")
|
||||||
|
private Resource dictionaryRegions;
|
||||||
|
|
||||||
|
@Bean(name = "remoteFormService")
|
||||||
|
@ConditionalOnProperty(name = "selenium.use-external-provider", havingValue = "true")
|
||||||
|
public SeleniumService lambdaTestRemoteFormService(SeleniumProperties seleniumProperties,
|
||||||
|
PaymentsProperties paymentsProperties,
|
||||||
|
LogEntriesToNetworkLogsConverter converter)
|
||||||
|
throws MalformedURLException {
|
||||||
|
URL lambdaTestUrl = buildLambdaTestUrl(seleniumProperties.getLambdaTest());
|
||||||
|
String formUrl = paymentsProperties.getFormUrl();
|
||||||
|
Long formTimeoutSec = paymentsProperties.getFormTimeoutSec();
|
||||||
|
DesiredCapabilities desiredCapabilities = SeleniumUtil.getLambdaTestCapabilities();
|
||||||
|
return new SeleniumService(lambdaTestUrl, formUrl, formTimeoutSec, desiredCapabilities, converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean(name = "remoteFormService")
|
||||||
|
@ConditionalOnProperty(name = "selenium.use-external-provider", havingValue = "false")
|
||||||
|
public SeleniumService seleniumRemoteFormService(SeleniumProperties seleniumProperties,
|
||||||
|
PaymentsProperties paymentsProperties,
|
||||||
|
LogEntriesToNetworkLogsConverter converter)
|
||||||
|
throws MalformedURLException {
|
||||||
|
URL seleniumUrl = new URL(seleniumProperties.getUrl() + ":" + seleniumProperties.getPort());
|
||||||
|
String formUrl = paymentsProperties.getFormUrl();
|
||||||
|
Long formTimeoutSec = paymentsProperties.getFormTimeoutSec();
|
||||||
|
DesiredCapabilities desiredCapabilities = SeleniumUtil.getCommonCapabilities();
|
||||||
|
return new SeleniumService(seleniumUrl, formUrl, formTimeoutSec, desiredCapabilities, converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public List<Region> regions(SeleniumProperties seleniumProperties) throws IOException {
|
||||||
|
return FileUtil.readRegions(dictionaryRegions)
|
||||||
|
.stream().filter(region -> seleniumProperties.getRegions().contains(region.getCode()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private URL buildLambdaTestUrl(SeleniumProperties.LambdaTestProperties lambdaTestProperties)
|
||||||
|
throws MalformedURLException {
|
||||||
|
String url = "https://" + lambdaTestProperties.getUser() + ":" + lambdaTestProperties.getToken() +
|
||||||
|
"@hub.lambdatest.com/wd/hub";
|
||||||
|
return new URL(url);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package dev.vality.beholder.config.properties;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Component
|
||||||
|
@Validated
|
||||||
|
@ConfigurationProperties(prefix = "keycloak")
|
||||||
|
public class KeycloakProperties {
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
|
private String user;
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
|
private String resource;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package dev.vality.beholder.config.properties;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import javax.validation.constraints.AssertTrue;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Component
|
||||||
|
@Validated
|
||||||
|
@ConfigurationProperties(prefix = "payments")
|
||||||
|
public class PaymentsProperties {
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
|
private String apiUrl;
|
||||||
|
|
||||||
|
private Long apiTimeoutSec = 10L;
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
|
private String formUrl;
|
||||||
|
|
||||||
|
private Long formTimeoutSec = 30L;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Request request;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Request {
|
||||||
|
@NotNull
|
||||||
|
private String shopId;
|
||||||
|
private Boolean createShopIfNotFound = false;
|
||||||
|
private Integer paymentInstitutionId;
|
||||||
|
private Integer categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssertTrue(message = "Check 'create-shop-if-not-found' option and related parameters")
|
||||||
|
private boolean isRequestConfigurationValid() {
|
||||||
|
return !request.createShopIfNotFound
|
||||||
|
|| (request.getPaymentInstitutionId() != null && request.getCategoryId() != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package dev.vality.beholder.config.properties;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
|
import javax.validation.constraints.AssertTrue;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Component
|
||||||
|
@Validated
|
||||||
|
@ConfigurationProperties(prefix = "selenium")
|
||||||
|
public class SeleniumProperties {
|
||||||
|
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
private Integer port;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Boolean useExternalProvider;
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
|
private List<String> regions;
|
||||||
|
|
||||||
|
private LambdaTestProperties lambdaTest;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class LambdaTestProperties {
|
||||||
|
|
||||||
|
private String user;
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssertTrue(message = "Check 'use-external-provider' option and related parameters")
|
||||||
|
private boolean isSeleniumConfigurationValid() {
|
||||||
|
return useExternalProvider && lambdaTest != null && !ObjectUtils.isEmpty(lambdaTest.token)
|
||||||
|
&& !ObjectUtils.isEmpty(lambdaTest.user)
|
||||||
|
|| !useExternalProvider && !ObjectUtils.isEmpty(url);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package dev.vality.beholder.converter;
|
||||||
|
|
||||||
|
import dev.vality.beholder.exception.BadFormatException;
|
||||||
|
import dev.vality.beholder.model.NetworkLog;
|
||||||
|
import dev.vality.beholder.model.NetworkMethod;
|
||||||
|
import org.openqa.selenium.logging.LogEntry;
|
||||||
|
import org.springframework.boot.configurationprocessor.json.JSONException;
|
||||||
|
import org.springframework.boot.configurationprocessor.json.JSONObject;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class LogEntriesToNetworkLogsConverter implements Converter<List<LogEntry>, List<NetworkLog>> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<NetworkLog> convert(List<LogEntry> source) {
|
||||||
|
Map<String, NetworkLog> networkLogs = new HashMap<>();
|
||||||
|
for (LogEntry logEntry : source) {
|
||||||
|
try {
|
||||||
|
//Message format doc: https://chromedevtools.github.io/devtools-protocol/tot/Network/
|
||||||
|
JSONObject log = new JSONObject(logEntry.getMessage());
|
||||||
|
JSONObject message = log.getJSONObject("message");
|
||||||
|
JSONObject params = message.getJSONObject("params");
|
||||||
|
if (params.has("requestId")) {
|
||||||
|
String requestId = params.getString("requestId");
|
||||||
|
String method = message.getString("method");
|
||||||
|
if (NetworkMethod.REQUEST_WILL_BE_SENT.getValue().equals(method)) {
|
||||||
|
double time = params.getDouble("timestamp") * 1000;
|
||||||
|
String resource = params.getJSONObject("request").getString("url");
|
||||||
|
networkLogs.put(requestId, new NetworkLog(resource, time, null));
|
||||||
|
} else if (NetworkMethod.LOADING_FINISHED.getValue().equals(method)) {
|
||||||
|
double time = params.getDouble("timestamp") * 1000;
|
||||||
|
NetworkLog networkLog =
|
||||||
|
networkLogs.getOrDefault(requestId, new NetworkLog(requestId, null, null));
|
||||||
|
networkLog.setEnd(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new BadFormatException("Error during parsing network logs:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ArrayList<>(networkLogs.values());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package dev.vality.beholder.exception;
|
||||||
|
|
||||||
|
public class BadFormatException extends RuntimeException {
|
||||||
|
|
||||||
|
public BadFormatException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BadFormatException(Throwable throwable) {
|
||||||
|
super(throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BadFormatException(String message, Throwable throwable) {
|
||||||
|
super(message, throwable);
|
||||||
|
}
|
||||||
|
}
|
13
src/main/java/dev/vality/beholder/model/Browser.java
Normal file
13
src/main/java/dev/vality/beholder/model/Browser.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package dev.vality.beholder.model;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public enum Browser {
|
||||||
|
CHROME("chrome");
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String label;
|
||||||
|
}
|
26
src/main/java/dev/vality/beholder/model/FormDataRequest.java
Normal file
26
src/main/java/dev/vality/beholder/model/FormDataRequest.java
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package dev.vality.beholder.model;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class FormDataRequest {
|
||||||
|
|
||||||
|
private String invoiceId;
|
||||||
|
|
||||||
|
private String invoiceAccessToken;
|
||||||
|
|
||||||
|
private Card cardInfo;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public static class Card {
|
||||||
|
@ToString.Exclude
|
||||||
|
private String pan;
|
||||||
|
private String expiration;
|
||||||
|
@ToString.Exclude
|
||||||
|
private String cvv;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package dev.vality.beholder.model;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FormDataResponse {
|
||||||
|
|
||||||
|
private FormDataRequest request;
|
||||||
|
|
||||||
|
private FormPerformance formPerformance;
|
||||||
|
|
||||||
|
private Browser browser;
|
||||||
|
|
||||||
|
private Region region;
|
||||||
|
|
||||||
|
private List<NetworkLog> networkLogs;
|
||||||
|
|
||||||
|
private boolean failed;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public static class FormPerformance {
|
||||||
|
|
||||||
|
private Double requestStartAt;
|
||||||
|
|
||||||
|
private Double responseStartAt;
|
||||||
|
|
||||||
|
private Double responseEndAt;
|
||||||
|
|
||||||
|
private Double domCompletedAt;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
src/main/java/dev/vality/beholder/model/NetworkLog.java
Normal file
16
src/main/java/dev/vality/beholder/model/NetworkLog.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package dev.vality.beholder.model;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class NetworkLog {
|
||||||
|
|
||||||
|
private String resource;
|
||||||
|
|
||||||
|
private Double start;
|
||||||
|
|
||||||
|
private Double end;
|
||||||
|
|
||||||
|
}
|
17
src/main/java/dev/vality/beholder/model/NetworkMethod.java
Normal file
17
src/main/java/dev/vality/beholder/model/NetworkMethod.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package dev.vality.beholder.model;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum NetworkMethod {
|
||||||
|
|
||||||
|
REQUEST_WILL_BE_SENT("Network.requestWillBeSent"),
|
||||||
|
|
||||||
|
RESPONSE_RECEIVED("Network.responseReceived"),
|
||||||
|
LOADING_FINISHED("Network.loadingFinished");
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
}
|
16
src/main/java/dev/vality/beholder/model/Region.java
Normal file
16
src/main/java/dev/vality/beholder/model/Region.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package dev.vality.beholder.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Region {
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
private String country;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return country + "[" + code + "]";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package dev.vality.beholder.security;
|
||||||
|
|
||||||
|
import dev.vality.beholder.config.properties.KeycloakProperties;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class KeycloakService {
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate;
|
||||||
|
private final KeycloakProperties keycloakProperties;
|
||||||
|
|
||||||
|
public String getUserToken() {
|
||||||
|
HttpEntity<MultiValueMap<String, String>> request =
|
||||||
|
new TokenRequest.Builder(keycloakProperties.getResource(), OAuth2Constants.PASSWORD)
|
||||||
|
.add("username", keycloakProperties.getUser())
|
||||||
|
.add("password", keycloakProperties.getPassword())
|
||||||
|
.build();
|
||||||
|
ResponseEntity<String> response =
|
||||||
|
restTemplate.postForEntity(keycloakProperties.getUrl(), request, String.class);
|
||||||
|
return response.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
36
src/main/java/dev/vality/beholder/security/TokenRequest.java
Normal file
36
src/main/java/dev/vality/beholder/security/TokenRequest.java
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package dev.vality.beholder.security;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.http.HttpEntity;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class TokenRequest {
|
||||||
|
public static class Builder {
|
||||||
|
MultiValueMap<String, String> data;
|
||||||
|
|
||||||
|
public Builder(String clientId, String grantType) {
|
||||||
|
data = new LinkedMultiValueMap<>();
|
||||||
|
data.put("client_id", Collections.singletonList(clientId));
|
||||||
|
data.put("grant_type", Collections.singletonList(grantType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder add(String key, String value) {
|
||||||
|
data.put(key, Collections.singletonList(value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpEntity<MultiValueMap<String, String>> build() {
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
return new HttpEntity<>(data, headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package dev.vality.beholder.service;
|
||||||
|
|
||||||
|
import dev.vality.beholder.model.FormDataRequest;
|
||||||
|
import dev.vality.beholder.model.FormDataResponse;
|
||||||
|
import dev.vality.beholder.model.Region;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class BeholderService {
|
||||||
|
|
||||||
|
private final PaymentsService paymentsService;
|
||||||
|
private final SeleniumService seleniumService;
|
||||||
|
private final MetricsService metricsService;
|
||||||
|
|
||||||
|
private final List<Region> regions;
|
||||||
|
|
||||||
|
@Scheduled(cron = "${schedule.cron:-}")
|
||||||
|
public void behold() {
|
||||||
|
log.info("Start sending requests from {} regions", regions);
|
||||||
|
List<FormDataResponse> responses = new ArrayList<>();
|
||||||
|
for (Region region : regions) {
|
||||||
|
FormDataRequest request;
|
||||||
|
try {
|
||||||
|
log.debug("Preparing request for {} region", region.getCountry());
|
||||||
|
request = paymentsService.prepareFormData();
|
||||||
|
log.debug("Request for {} region successfully prepared", region.getCountry());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Unable to prepare request for {}:", region.getCountry(), e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
FormDataResponse response = seleniumService.executePaymentRequest(request, region);
|
||||||
|
responses.add(response);
|
||||||
|
log.debug("Metrics for {} region saved", region.getCountry());
|
||||||
|
}
|
||||||
|
metricsService.updateMetrics(responses);
|
||||||
|
log.info("Finished processing requests from {} regions", regions);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
158
src/main/java/dev/vality/beholder/service/MetricsService.java
Normal file
158
src/main/java/dev/vality/beholder/service/MetricsService.java
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
package dev.vality.beholder.service;
|
||||||
|
|
||||||
|
import dev.vality.beholder.model.FormDataResponse;
|
||||||
|
import dev.vality.beholder.util.MetricUtil;
|
||||||
|
import io.micrometer.core.instrument.*;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class MetricsService {
|
||||||
|
|
||||||
|
private final MeterRegistry meterRegistry;
|
||||||
|
private final MultiGauge resourcesLoadingTimings;
|
||||||
|
private final MultiGauge formDataWaitingDurationGauges;
|
||||||
|
private final MultiGauge formDataReceivingDuration;
|
||||||
|
private final MultiGauge formDomCompleteDuration;
|
||||||
|
private final Map<String, Counter> formLoadingCounters;
|
||||||
|
private final Map<String, Counter> formLoadingFailedCounters;
|
||||||
|
|
||||||
|
|
||||||
|
public MetricsService(MeterRegistry meterRegistry) {
|
||||||
|
|
||||||
|
this.meterRegistry = meterRegistry;
|
||||||
|
|
||||||
|
this.resourcesLoadingTimings = MultiGauge.builder("beholder_form_resource_loading_duration")
|
||||||
|
.description("Resources uploading time")
|
||||||
|
.baseUnit("millis")
|
||||||
|
.register(meterRegistry);
|
||||||
|
|
||||||
|
this.formDataWaitingDurationGauges = MultiGauge.builder("beholder_form_waiting_response_duration")
|
||||||
|
.description("Time between sending request and first received byte of data")
|
||||||
|
.baseUnit("millis")
|
||||||
|
.register(meterRegistry);
|
||||||
|
|
||||||
|
this.formDataReceivingDuration = MultiGauge.builder("beholder_form_receiving_response_duration")
|
||||||
|
.description("Time between receiving first and last byte of data")
|
||||||
|
.baseUnit("millis")
|
||||||
|
.register(meterRegistry);
|
||||||
|
|
||||||
|
this.formDomCompleteDuration = MultiGauge.builder("beholder_form_dom_complete_duration")
|
||||||
|
.description("Time between sending request and fully rendered DOM")
|
||||||
|
.baseUnit("millis")
|
||||||
|
.register(meterRegistry);
|
||||||
|
|
||||||
|
this.formLoadingCounters = new HashMap<>();
|
||||||
|
|
||||||
|
this.formLoadingFailedCounters = new HashMap<>();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateMetrics(List<FormDataResponse> formDataResponses) {
|
||||||
|
log.debug("Updating beholder metrics started");
|
||||||
|
updateWaitingResponseDuration(formDataResponses);
|
||||||
|
updateFormDataReceivingDuration(formDataResponses);
|
||||||
|
updateFormDomCompleteDuration(formDataResponses);
|
||||||
|
updateResourceLoadingDuration(formDataResponses);
|
||||||
|
updateFormLoadingRequestsTotal(formDataResponses);
|
||||||
|
updateFormLoadingFailedRequestsTotal(formDataResponses);
|
||||||
|
log.debug("Updating beholder metrics finished");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateWaitingResponseDuration(List<FormDataResponse> formDataResponses) {
|
||||||
|
formDataWaitingDurationGauges.register(
|
||||||
|
formDataResponses.stream()
|
||||||
|
.filter(Predicate.not(FormDataResponse::isFailed))
|
||||||
|
.map(formDataResponse -> MultiGauge.Row.of(
|
||||||
|
MetricUtil.createCommonTags(formDataResponse),
|
||||||
|
MetricUtil.calculateWaitingResponseDuration(formDataResponse.getFormPerformance())))
|
||||||
|
.collect(toList()),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFormDataReceivingDuration(List<FormDataResponse> formDataResponses) {
|
||||||
|
formDataReceivingDuration.register(
|
||||||
|
formDataResponses.stream()
|
||||||
|
.filter(Predicate.not(FormDataResponse::isFailed))
|
||||||
|
.map(formDataResponse -> MultiGauge.Row.of(
|
||||||
|
MetricUtil.createCommonTags(formDataResponse),
|
||||||
|
MetricUtil.calculateDataReceivingDuration(formDataResponse.getFormPerformance())))
|
||||||
|
.collect(toList()),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFormDomCompleteDuration(List<FormDataResponse> formDataResponses) {
|
||||||
|
formDomCompleteDuration.register(
|
||||||
|
formDataResponses.stream()
|
||||||
|
.filter(Predicate.not(FormDataResponse::isFailed))
|
||||||
|
.map(formDataResponse -> MultiGauge.Row.of(
|
||||||
|
MetricUtil.createCommonTags(formDataResponse),
|
||||||
|
MetricUtil.calculateDomCompleteDuration(formDataResponse.getFormPerformance())))
|
||||||
|
.collect(toList()),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFormLoadingRequestsTotal(List<FormDataResponse> formDataResponses) {
|
||||||
|
for (FormDataResponse response : formDataResponses) {
|
||||||
|
String id = MetricUtil.getCounterId(response);
|
||||||
|
Counter counter = formLoadingCounters.getOrDefault(id,
|
||||||
|
Counter.builder("beholder_form_loading_requests")
|
||||||
|
.description("Total requests for form upload")
|
||||||
|
.tags(MetricUtil.createCommonTags(response))
|
||||||
|
.baseUnit("total")
|
||||||
|
.register(meterRegistry));
|
||||||
|
counter.increment();
|
||||||
|
formLoadingCounters.put(id, counter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFormLoadingFailedRequestsTotal(List<FormDataResponse> formDataResponses) {
|
||||||
|
for (FormDataResponse response : formDataResponses) {
|
||||||
|
if (response.isFailed()) {
|
||||||
|
String id = MetricUtil.getCounterId(response);
|
||||||
|
Counter counter = formLoadingFailedCounters.getOrDefault(id,
|
||||||
|
Counter.builder("beholder_form_loading_failed")
|
||||||
|
.description("Total failed requests for form upload")
|
||||||
|
.tags(MetricUtil.createCommonTags(response))
|
||||||
|
.baseUnit("total")
|
||||||
|
.register(meterRegistry));
|
||||||
|
counter.increment();
|
||||||
|
formLoadingFailedCounters.put(id, counter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateResourceLoadingDuration(List<FormDataResponse> formDataResponses) {
|
||||||
|
formDataResponses.stream()
|
||||||
|
.filter(Predicate.not(FormDataResponse::isFailed))
|
||||||
|
.forEach(
|
||||||
|
formDataResponse ->
|
||||||
|
resourcesLoadingTimings.register(
|
||||||
|
formDataResponse.getNetworkLogs().stream()
|
||||||
|
.map(networkLog -> MultiGauge.Row.of(
|
||||||
|
MetricUtil.createCommonTags(formDataResponse)
|
||||||
|
.and("resource",
|
||||||
|
MetricUtil.getNormalisedPath(networkLog,
|
||||||
|
formDataResponse.getRequest()
|
||||||
|
.getInvoiceId(),
|
||||||
|
formDataResponse.getRequest()
|
||||||
|
.getInvoiceAccessToken())),
|
||||||
|
MetricUtil.calculateRequestDuration(networkLog)))
|
||||||
|
.collect(toList()),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package dev.vality.beholder.service;
|
||||||
|
|
||||||
|
import dev.vality.beholder.config.properties.PaymentsProperties;
|
||||||
|
import dev.vality.beholder.model.FormDataRequest;
|
||||||
|
import dev.vality.beholder.security.KeycloakService;
|
||||||
|
import dev.vality.beholder.util.PaymentsUtil;
|
||||||
|
import dev.vality.swag.payments.ApiClient;
|
||||||
|
import dev.vality.swag.payments.api.ClaimsApi;
|
||||||
|
import dev.vality.swag.payments.api.InvoicesApi;
|
||||||
|
import dev.vality.swag.payments.api.PartiesApi;
|
||||||
|
import dev.vality.swag.payments.api.ShopsApi;
|
||||||
|
import dev.vality.swag.payments.model.*;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.HttpStatusCodeException;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PaymentsService {
|
||||||
|
|
||||||
|
private final ApiClient apiClient;
|
||||||
|
private final PartiesApi partiesApi;
|
||||||
|
private final ShopsApi shopsApi;
|
||||||
|
private final InvoicesApi invoicesApi;
|
||||||
|
private final ClaimsApi claimsApi;
|
||||||
|
private final KeycloakService keycloakService;
|
||||||
|
private final PaymentsProperties paymentsProperties;
|
||||||
|
|
||||||
|
public FormDataRequest prepareFormData() {
|
||||||
|
apiClient.setApiKey(keycloakService.getUserToken());
|
||||||
|
String shopId = getShopId();
|
||||||
|
InvoiceParams invoiceParams = PaymentsUtil.createInvoiceParams(shopId);
|
||||||
|
InvoiceAndToken invoiceAndToken = invoicesApi.createInvoice(PaymentsUtil.getRequestId(), invoiceParams,
|
||||||
|
PaymentsUtil.getRequestDeadline(paymentsProperties.getApiTimeoutSec()));
|
||||||
|
return createFormDataRequest(invoiceAndToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getShopId() {
|
||||||
|
Party party = partiesApi.getMyParty(PaymentsUtil.getRequestId(),
|
||||||
|
PaymentsUtil.getRequestDeadline(paymentsProperties.getApiTimeoutSec()));
|
||||||
|
var request = paymentsProperties.getRequest();
|
||||||
|
String shopId = request.getShopId();
|
||||||
|
try {
|
||||||
|
shopsApi.getShopByIDForParty(PaymentsUtil.getRequestId(), shopId, party.getId(),
|
||||||
|
PaymentsUtil.getRequestDeadline(paymentsProperties.getApiTimeoutSec()));
|
||||||
|
} catch (HttpStatusCodeException httpStatusCodeException) {
|
||||||
|
if (!isNotFoundError(httpStatusCodeException) || !request.getCreateShopIfNotFound()) {
|
||||||
|
throw httpStatusCodeException;
|
||||||
|
}
|
||||||
|
sendShopCreationClaim(request, shopId);
|
||||||
|
}
|
||||||
|
return shopId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendShopCreationClaim(PaymentsProperties.Request request, String shopId) {
|
||||||
|
ClaimChangeset changeset = PaymentsUtil.buildCreateShopClaim(request.getPaymentInstitutionId(),
|
||||||
|
shopId, request.getCategoryId());
|
||||||
|
claimsApi.createClaim(PaymentsUtil.getRequestId(), changeset,
|
||||||
|
PaymentsUtil.getRequestDeadline(paymentsProperties.getApiTimeoutSec()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private FormDataRequest createFormDataRequest(InvoiceAndToken invoiceAndToken) {
|
||||||
|
FormDataRequest formDataRequest = new FormDataRequest();
|
||||||
|
formDataRequest.setCardInfo(FormDataRequest.Card.builder()
|
||||||
|
.pan(PaymentsUtil.TEST_CARD_PAN)
|
||||||
|
.cvv(PaymentsUtil.TEST_CARD_CVV)
|
||||||
|
.expiration(PaymentsUtil.TEST_CARD_EXPIRATION).build());
|
||||||
|
formDataRequest.setInvoiceId(invoiceAndToken.getInvoice().getId());
|
||||||
|
formDataRequest.setInvoiceAccessToken(invoiceAndToken.getInvoiceAccessToken().getPayload());
|
||||||
|
return formDataRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNotFoundError(HttpStatusCodeException httpStatusCodeException) {
|
||||||
|
return HttpStatus.NOT_FOUND.equals(httpStatusCodeException.getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
118
src/main/java/dev/vality/beholder/service/SeleniumService.java
Normal file
118
src/main/java/dev/vality/beholder/service/SeleniumService.java
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package dev.vality.beholder.service;
|
||||||
|
|
||||||
|
import dev.vality.beholder.converter.LogEntriesToNetworkLogsConverter;
|
||||||
|
import dev.vality.beholder.model.*;
|
||||||
|
import dev.vality.beholder.util.SeleniumUtil;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.openqa.selenium.By;
|
||||||
|
import org.openqa.selenium.WebElement;
|
||||||
|
import org.openqa.selenium.logging.LogEntries;
|
||||||
|
import org.openqa.selenium.logging.LogType;
|
||||||
|
import org.openqa.selenium.remote.DesiredCapabilities;
|
||||||
|
import org.openqa.selenium.remote.RemoteWebDriver;
|
||||||
|
import org.openqa.selenium.support.ui.ExpectedConditions;
|
||||||
|
import org.openqa.selenium.support.ui.WebDriverWait;
|
||||||
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SeleniumService {
|
||||||
|
|
||||||
|
private final URL seleniumUrl;
|
||||||
|
private final String formUrl;
|
||||||
|
private final Long formTimeoutSec;
|
||||||
|
private final DesiredCapabilities desiredCapabilities;
|
||||||
|
private final LogEntriesToNetworkLogsConverter logEntriesToNetworkLogsConverter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Browser browser = Browser.CHROME;
|
||||||
|
|
||||||
|
public FormDataResponse executePaymentRequest(FormDataRequest formDataRequest, Region region) {
|
||||||
|
DesiredCapabilities capabilities = new DesiredCapabilities(desiredCapabilities);
|
||||||
|
updateCapabilities(capabilities, region);
|
||||||
|
|
||||||
|
RemoteWebDriver driver = null;
|
||||||
|
try {
|
||||||
|
driver = new RemoteWebDriver(seleniumUrl, capabilities);
|
||||||
|
driver.get(prepareParams(formDataRequest));
|
||||||
|
|
||||||
|
//noinspection rawtypes
|
||||||
|
ArrayList performanceMetrics = (ArrayList) driver.executeScript(SeleniumUtil.PERFORMANCE_SCRIPT);
|
||||||
|
fillAndSendPaymentRequest(driver, formDataRequest.getCardInfo());
|
||||||
|
|
||||||
|
LogEntries les = driver.manage().logs().get(LogType.PERFORMANCE);
|
||||||
|
List<NetworkLog> networkLogs = logEntriesToNetworkLogsConverter.convert(les.getAll());
|
||||||
|
|
||||||
|
return FormDataResponse.builder()
|
||||||
|
.networkLogs(networkLogs)
|
||||||
|
.request(formDataRequest)
|
||||||
|
.formPerformance(
|
||||||
|
FormDataResponse.FormPerformance.builder()
|
||||||
|
.requestStartAt(castToDouble(performanceMetrics.get(0)))
|
||||||
|
.responseStartAt(castToDouble(performanceMetrics.get(1)))
|
||||||
|
.responseEndAt(castToDouble(performanceMetrics.get(2)))
|
||||||
|
.domCompletedAt(castToDouble(performanceMetrics.get(3)))
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.region(region)
|
||||||
|
.browser(browser)
|
||||||
|
.build();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error during sending request from {}:", region.getCountry(), e);
|
||||||
|
} finally {
|
||||||
|
if (driver != null) {
|
||||||
|
driver.quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FormDataResponse.builder()
|
||||||
|
.region(region)
|
||||||
|
.browser(browser)
|
||||||
|
.failed(true)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCapabilities(DesiredCapabilities capabilities, Region region) {
|
||||||
|
capabilities.setCapability("browser", browser.getLabel());
|
||||||
|
capabilities.setCapability("geoLocation", region.getCode());
|
||||||
|
capabilities.setCapability("name", String.format("Payment from %s", region.getCountry()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillAndSendPaymentRequest(RemoteWebDriver driver, FormDataRequest.Card card) {
|
||||||
|
WebElement cardNumInput = new WebDriverWait(driver, formTimeoutSec).until(
|
||||||
|
ExpectedConditions.visibilityOfElementLocated(By.ById.id("card-number-input")));
|
||||||
|
cardNumInput.sendKeys(card.getPan());
|
||||||
|
driver.findElementById("expire-date-input").sendKeys(card.getExpiration());
|
||||||
|
driver.findElementById("secure-code-input").sendKeys(card.getCvv());
|
||||||
|
driver.findElementById("card-holder-input").sendKeys("Ivan Ivanov");
|
||||||
|
driver.findElementById("email-input").sendKeys("test@test.com");
|
||||||
|
driver.findElementById("pay-btn").click();
|
||||||
|
new WebDriverWait(driver, formTimeoutSec).until(
|
||||||
|
ExpectedConditions.visibilityOfElementLocated(By.ById.id("success-icon")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Double castToDouble(Object object) {
|
||||||
|
if (object instanceof Double) {
|
||||||
|
return (Double) object;
|
||||||
|
} else if (object instanceof Long) {
|
||||||
|
return ((Long) object).doubleValue();
|
||||||
|
} else {
|
||||||
|
log.warn("Unable to cast {} to double", object);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String prepareParams(FormDataRequest formDataRequest) {
|
||||||
|
return UriComponentsBuilder.fromHttpUrl(formUrl)
|
||||||
|
.queryParam("invoiceAccessToken", formDataRequest.getInvoiceAccessToken())
|
||||||
|
.queryParam("invoiceID", formDataRequest.getInvoiceId())
|
||||||
|
.build()
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
22
src/main/java/dev/vality/beholder/util/FileUtil.java
Normal file
22
src/main/java/dev/vality/beholder/util/FileUtil.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package dev.vality.beholder.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import dev.vality.beholder.model.Region;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class FileUtil {
|
||||||
|
|
||||||
|
public static List<Region> readRegions(Resource resource) throws IOException {
|
||||||
|
final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
return objectMapper.readValue(
|
||||||
|
resource.getFile(),
|
||||||
|
new TypeReference<>() {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
58
src/main/java/dev/vality/beholder/util/MetricUtil.java
Normal file
58
src/main/java/dev/vality/beholder/util/MetricUtil.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package dev.vality.beholder.util;
|
||||||
|
|
||||||
|
import dev.vality.beholder.model.FormDataResponse;
|
||||||
|
import dev.vality.beholder.model.NetworkLog;
|
||||||
|
import io.micrometer.core.instrument.Tags;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@UtilityClass
|
||||||
|
public class MetricUtil {
|
||||||
|
|
||||||
|
public static String getNormalisedPath(NetworkLog networkLog, String invoiceId, String invoiceToken) {
|
||||||
|
String path = networkLog.getResource();
|
||||||
|
path = path.replaceAll(invoiceId, "{invoice_id}")
|
||||||
|
.replaceAll(invoiceToken, "{invoice_token}");
|
||||||
|
if (path.contains("nocache=")) {
|
||||||
|
path = path.substring(0, path.length() - 13);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double calculateRequestDuration(NetworkLog networkLog) {
|
||||||
|
return calculateDiff(networkLog.getStart(), networkLog.getEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double calculateWaitingResponseDuration(FormDataResponse.FormPerformance performance) {
|
||||||
|
return calculateDiff(performance.getRequestStartAt(), performance.getResponseStartAt());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double calculateDataReceivingDuration(FormDataResponse.FormPerformance performance) {
|
||||||
|
return calculateDiff(performance.getResponseStartAt(), performance.getResponseEndAt());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double calculateDomCompleteDuration(FormDataResponse.FormPerformance performance) {
|
||||||
|
return calculateDiff(performance.getRequestStartAt(), performance.getDomCompletedAt());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double calculateDiff(Double from, Double to) {
|
||||||
|
if (isNumber(from) && isNumber(to)) {
|
||||||
|
return Math.round(to - from);
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNumber(Double value) {
|
||||||
|
return value != null && !value.isNaN();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tags createCommonTags(FormDataResponse formDataResponse) {
|
||||||
|
return Tags.of("browser", formDataResponse.getBrowser().getLabel(),
|
||||||
|
"region", formDataResponse.getRegion().getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCounterId(FormDataResponse formDataResponse) {
|
||||||
|
return String.join("_", formDataResponse.getBrowser().name(), formDataResponse.getRegion().getCountry());
|
||||||
|
}
|
||||||
|
}
|
136
src/main/java/dev/vality/beholder/util/PaymentsUtil.java
Normal file
136
src/main/java/dev/vality/beholder/util/PaymentsUtil.java
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package dev.vality.beholder.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import dev.vality.swag.payments.model.*;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class PaymentsUtil {
|
||||||
|
|
||||||
|
public static final String TEST_CARD_PAN = "4242424242424242";
|
||||||
|
public static final String TEST_CARD_EXPIRATION = "12/24";
|
||||||
|
public static final String TEST_CARD_CVV = "123";
|
||||||
|
|
||||||
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
public static final String CURRENCY_CODE = "RUB";
|
||||||
|
|
||||||
|
public static PartyModification createShopModification(String shopId, String contractId, String payoutToolId) {
|
||||||
|
return new ShopCreation()
|
||||||
|
.details(
|
||||||
|
new ShopDetails()
|
||||||
|
.name("OOOBlackMaster")
|
||||||
|
.description("Goods for education"))
|
||||||
|
.location(
|
||||||
|
new ShopLocationUrl()
|
||||||
|
.url("http://all-time-favourite-spinners.com/"))
|
||||||
|
.contractID(contractId)
|
||||||
|
.payoutToolID(payoutToolId)
|
||||||
|
.shopID(shopId)
|
||||||
|
.shopModificationType(ShopModification.ShopModificationTypeEnum.SHOPCREATION)
|
||||||
|
.partyModificationType(PartyModification.PartyModificationTypeEnum.SHOPMODIFICATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PartyModification createShopAccountCreationModification(String shopId) {
|
||||||
|
return new ShopAccountCreation()
|
||||||
|
.currency(CURRENCY_CODE)
|
||||||
|
.shopID(shopId)
|
||||||
|
.shopModificationType(ShopModification.ShopModificationTypeEnum.SHOPACCOUNTCREATION)
|
||||||
|
.partyModificationType(PartyModification.PartyModificationTypeEnum.SHOPMODIFICATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PartyModification createShopCategoryChangeModification(String shopId, Integer categoryId) {
|
||||||
|
return new ShopCategoryChange()
|
||||||
|
.categoryID(categoryId)
|
||||||
|
.shopID(shopId)
|
||||||
|
.shopModificationType(ShopModification.ShopModificationTypeEnum.SHOPCATEGORYCHANGE)
|
||||||
|
.partyModificationType(PartyModification.PartyModificationTypeEnum.SHOPMODIFICATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClaimChangeset buildCreateShopClaim(Integer paymentInstitutionId, String shopId, Integer categoryId) {
|
||||||
|
PartyModification contractCreation = createContractModification(shopId, paymentInstitutionId);
|
||||||
|
PartyModification contractModification = createPayoutToolModification(shopId);
|
||||||
|
PartyModification shopModification = createShopModification(shopId, shopId, shopId);
|
||||||
|
PartyModification categoryChange = createShopCategoryChangeModification(shopId, categoryId);
|
||||||
|
PartyModification accountChange = createShopAccountCreationModification(shopId);
|
||||||
|
|
||||||
|
ClaimChangeset changeset = new ClaimChangeset();
|
||||||
|
changeset.add(contractCreation);
|
||||||
|
changeset.add(contractModification);
|
||||||
|
changeset.add(shopModification);
|
||||||
|
changeset.add(categoryChange);
|
||||||
|
changeset.add(accountChange);
|
||||||
|
return changeset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PartyModification createContractModification(String contractId, Integer paymentInstitutionId) {
|
||||||
|
return new ContractCreation()
|
||||||
|
.contractor(createContractor())
|
||||||
|
.paymentInstitutionID(paymentInstitutionId)
|
||||||
|
.contractID(contractId)
|
||||||
|
.contractModificationType(ContractModification.ContractModificationTypeEnum.CONTRACTCREATION)
|
||||||
|
.partyModificationType(PartyModification.PartyModificationTypeEnum.CONTRACTMODIFICATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Contractor createContractor() {
|
||||||
|
return new RussianLegalEntity()
|
||||||
|
.registeredName("testRegisteredName")
|
||||||
|
.registeredNumber("1234567890123")
|
||||||
|
.inn("1234567890")
|
||||||
|
.actualAddress("testActualAddress")
|
||||||
|
.postAddress("testPostAddress")
|
||||||
|
.representativePosition("testRepresentativePosition")
|
||||||
|
.representativeFullName("testRepresentativeFullName")
|
||||||
|
.representativeDocument("testRepresentativeDocument")
|
||||||
|
.bankAccount(createBankAccount())
|
||||||
|
.entityType(LegalEntity.EntityTypeEnum.RUSSIANLEGALENTITY)
|
||||||
|
.contractorType(Contractor.ContractorTypeEnum.LEGALENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PartyModification createPayoutToolModification(String contractId) {
|
||||||
|
return new ContractPayoutToolCreation()
|
||||||
|
.payoutToolID(contractId)
|
||||||
|
.currency(CURRENCY_CODE)
|
||||||
|
.details(new PayoutToolDetailsBankAccount()
|
||||||
|
.account("12345678901234567890")
|
||||||
|
.bankName("testBankName")
|
||||||
|
.bankPostAccount("12345678901234567890")
|
||||||
|
.bankBik("123456789")
|
||||||
|
)
|
||||||
|
.contractID(contractId)
|
||||||
|
.contractModificationType(ContractModification.ContractModificationTypeEnum.CONTRACTPAYOUTTOOLCREATION)
|
||||||
|
.partyModificationType(PartyModification.PartyModificationTypeEnum.CONTRACTMODIFICATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BankAccount createBankAccount() {
|
||||||
|
return new BankAccount()
|
||||||
|
.account("12345678901234567890")
|
||||||
|
.bankName("testBankName")
|
||||||
|
.bankPostAccount("12345678901234567890")
|
||||||
|
.bankBik("123456789");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InvoiceParams createInvoiceParams(String shopId) {
|
||||||
|
return new InvoiceParams()
|
||||||
|
.shopID(shopId)
|
||||||
|
.dueDate(OffsetDateTime.now().plusDays(1))
|
||||||
|
.currency(CURRENCY_CODE)
|
||||||
|
.product("Order num 12345")
|
||||||
|
.amount(1000L)
|
||||||
|
.metadata(MAPPER.createObjectNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRequestId() {
|
||||||
|
return RandomUtil.generateString(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRequestDeadline(long seconds) {
|
||||||
|
return ZonedDateTime.now()
|
||||||
|
.plusSeconds(seconds)
|
||||||
|
.format(DateTimeFormatter.ISO_INSTANT);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
22
src/main/java/dev/vality/beholder/util/RandomUtil.java
Normal file
22
src/main/java/dev/vality/beholder/util/RandomUtil.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package dev.vality.beholder.util;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class RandomUtil {
|
||||||
|
|
||||||
|
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
private static final Random STRING_RANDOM = new Random();
|
||||||
|
|
||||||
|
public String generateString(int length) {
|
||||||
|
StringBuilder builder = new StringBuilder(length);
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
builder.append(ALPHABET.charAt(STRING_RANDOM.nextInt(ALPHABET.length())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
40
src/main/java/dev/vality/beholder/util/SeleniumUtil.java
Normal file
40
src/main/java/dev/vality/beholder/util/SeleniumUtil.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package dev.vality.beholder.util;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.openqa.selenium.logging.LogType;
|
||||||
|
import org.openqa.selenium.logging.LoggingPreferences;
|
||||||
|
import org.openqa.selenium.remote.CapabilityType;
|
||||||
|
import org.openqa.selenium.remote.DesiredCapabilities;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class SeleniumUtil {
|
||||||
|
|
||||||
|
public static final String PERFORMANCE_SCRIPT = """
|
||||||
|
var data = new Array();
|
||||||
|
var navigation = window.performance.getEntriesByType("navigation")[0];
|
||||||
|
data[0] = navigation.requestStart;
|
||||||
|
data[1] = navigation.responseStart;
|
||||||
|
data[2] = navigation.responseEnd;
|
||||||
|
data[3] = navigation.domComplete;
|
||||||
|
return data;""";
|
||||||
|
|
||||||
|
public static DesiredCapabilities getCommonCapabilities() {
|
||||||
|
var capabilities = new DesiredCapabilities();
|
||||||
|
capabilities.setCapability("build", "Simple payment test");
|
||||||
|
var logPrefs = new LoggingPreferences();
|
||||||
|
logPrefs.enable(LogType.PERFORMANCE, Level.ALL);
|
||||||
|
capabilities.setCapability(CapabilityType.LOGGING_PREFS, logPrefs);
|
||||||
|
return capabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DesiredCapabilities getLambdaTestCapabilities() {
|
||||||
|
var capabilities = getCommonCapabilities();
|
||||||
|
capabilities.setCapability("network", false);
|
||||||
|
capabilities.setCapability("visual", false);
|
||||||
|
capabilities.setCapability("video", false);
|
||||||
|
capabilities.setCapability("console", false);
|
||||||
|
return capabilities;
|
||||||
|
}
|
||||||
|
}
|
62
src/main/resources/application.yml
Normal file
62
src/main/resources/application.yml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
server:
|
||||||
|
port: '@server.port@'
|
||||||
|
|
||||||
|
management:
|
||||||
|
server:
|
||||||
|
port: '@management.port@'
|
||||||
|
metrics:
|
||||||
|
tags:
|
||||||
|
application: '@project.name@'
|
||||||
|
export:
|
||||||
|
prometheus:
|
||||||
|
enabled: true
|
||||||
|
endpoint:
|
||||||
|
health:
|
||||||
|
show-details: always
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
prometheus:
|
||||||
|
enabled: true
|
||||||
|
endpoints:
|
||||||
|
web:
|
||||||
|
exposure:
|
||||||
|
include: health,info,prometheus
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: '@project.name@'
|
||||||
|
output:
|
||||||
|
ansi:
|
||||||
|
enabled: always
|
||||||
|
info:
|
||||||
|
version: '@project.version@'
|
||||||
|
stage: dev
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
root: INFO
|
||||||
|
|
||||||
|
schedule:
|
||||||
|
cron: '0 */5 * * * *' #every 5 minutes
|
||||||
|
keycloak:
|
||||||
|
url: http://127.0.0.1
|
||||||
|
user: user
|
||||||
|
password: admin
|
||||||
|
resource: external
|
||||||
|
payments:
|
||||||
|
api-url: http://127.0.0.1
|
||||||
|
api-timeout-sec: 30
|
||||||
|
form-url: http://127.0.0.1
|
||||||
|
form-timeout-sec: 30
|
||||||
|
request:
|
||||||
|
shop-id: 1
|
||||||
|
create-shop-if-not-found: true
|
||||||
|
payment-institution-id: 1
|
||||||
|
category-id: 1
|
||||||
|
selenium:
|
||||||
|
url: http://127.0.0.1
|
||||||
|
port: 4444
|
||||||
|
use-external-provider: false
|
||||||
|
regions:
|
||||||
|
- BR
|
||||||
|
dictionary:
|
||||||
|
regions: classpath:regions.json
|
140
src/main/resources/banner.txt
Normal file
140
src/main/resources/banner.txt
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
|
||||||
|
` `
|
||||||
|
`` ` ` ` `
|
||||||
|
` ` ,***,```
|
||||||
|
:*; `;*:````
|
||||||
|
:i` ` .i*```
|
||||||
|
`.*` ``..` :*``
|
||||||
|
`*`` `:#zz, `*,``
|
||||||
|
,; ```nx#i#` .+.`
|
||||||
|
:: ``nxz#+` `z,``
|
||||||
|
:;` `:#nz: ` +:.`
|
||||||
|
,*`````.,,` #;.`
|
||||||
|
.+,` `` `` `n;.`
|
||||||
|
.i;```` ```` i+:.`
|
||||||
|
`.z:. ` ` ``i#;:``
|
||||||
|
`.+#:,````:#+*:,`
|
||||||
|
``:*z+z#+Mn+i;,``
|
||||||
|
`.+;x@+n**;:,.`
|
||||||
|
`i;nzi#*::.``
|
||||||
|
``:;*z**;,.`
|
||||||
|
:iiiz;:.`
|
||||||
|
`;:,:#;,`
|
||||||
|
`*i;*+;.`
|
||||||
|
` ` ::;,i+:,` ` ` ` `` `
|
||||||
|
`````` `;:..:*:.` ` ` .`` `
|
||||||
|
`:*;;+i` ` ` `*,`.*+:.` ```i*;i*,` ``
|
||||||
|
`,;+;;+#i;*i.`` *,,`i**,. i*` ```ii. ` `.``
|
||||||
|
`:ii;:::*nM#;:i+; .i,``.ni.` ;:``+##. i;``:*;;;i*,``
|
||||||
|
` ```` ,++:```+nx; ` :i.````:i:`.i+:.` .i``ixxzz.`+i+;:;,,,`;*:`.::;i+z++i+i*+**i.`` `
|
||||||
|
`````ii:`.:;M#,` `` `i:`` `*i` .#i:.` i: `*xx#z,`,+;,.` ` ` `*#i:ii:++##zi.````:*.``
|
||||||
|
`` ` ` `:;:``#++M# ``..``,i.``,*;``i#;,` #, .+zx*.` z``` .````i;``,`.,;;nn`````` .*.```
|
||||||
|
` ` ``````:*. .**nM;```:z#i ,*,``*;, ,;#:.` `#. ``::````* ``++n+`*:````.i;+#x: ```````*,.``
|
||||||
|
`,:::::.```+;.`,:n#x: `:zzn#.:+,.,;+. `**:.` `+, ` ,;,:;:::;n, ``.:i;i;:i` ``.:,``.+.`
|
||||||
|
,*::,.,:i,``*;` ;*z+z, `inz*#.*+,.:ii, `+;,. ` ``#. ```` `*.i:####z:` .,+#####: ` ;#zz: `z:``
|
||||||
|
` ` `:i```` ```*,.*i` .,zi+:``:nxz;;zi,.+*i.``+;,` `,z:` ` `:+...x**i+:, `.*ii#ii;;#. `,zM*i#``z:.`
|
||||||
|
,i ``,**.`,#,i:i ``n;;+ ``:;:;z*;,;;*::``+;,` ,*;*:` .:iz. .i+i:;+:. `*. :#;,,,*, :zWz##``n:.`
|
||||||
|
i.` `;xn#n` i;ii#. :#;;zi;;i##*;,.+:*+*``+i,```.,;;i..+@@#i*i+```:#i;,+,, `;n.`.*;.`.i;` `*zxz:`*+:.`
|
||||||
|
` `.* ``.zn+,#:`;*:#:+ `:#:;*+##*i;..`++M#ii;++**ii;::. .,iz#:z*+: ,zi:;i:```x:z.`+:.``,+`` `:::`izi,.`
|
||||||
|
;.` .znWzn,`:*:;*ii` .*i:;;::,.`,i*,`` ` ` ` `,iz;in#:i ```+:;*,, +#ii. +:.```,z: ``.:+#i:.``
|
||||||
|
i` ` :#nn+` :*::*:*,` `,**,.,,.`i;...``` `` ` `,:*;;z#*#.i`` `#;+:: ``#++;, ;i.```.;*#####+i;,`
|
||||||
|
`i` ` ``````;+:.,#:;,```;**,.`.*i.`` ` ` ` ``,,:*;+z*i;*,z; z+.` ;#iz;i`.z,.` `.,;iiii;:,.`
|
||||||
|
` ,i ` ``````zi,..:#;+:`.`#;#..+*i:` `..,..,`,:+z+#+i;:,;:i. ` :+,` ` **;z:#..z:``` ..,::::,.``
|
||||||
|
``*: `` ,n#i,`..:#;*i.:,Mi##;+..``` `,:**ii++nx#**i;;:,...+,+.;+:`` .ni:#;n+:i+i,` ``.``.``
|
||||||
|
`:#, `.i*#z*:,``..:+;#x*,**z*,.` ..*i;:;;##*i;i:::,.````*in::` `` `+*;,*iWx;:;:i:`` ````
|
||||||
|
`.;*zznW+#n+i;,.` `.;z;#x++W:#..` `;:zi,.```,+*;,,...``.::i*, ` ` *#;:.*#;` ``` +,```
|
||||||
|
``:i+xxi+#*i;,.`` `*+ii**xxni+#.````,,x:;:.` `..+:,````:*;:,``` ` ` i#*:,`#,` `````;i```
|
||||||
|
`,;;;i;#*;:,```:ii.,,:;:izn:#:... `:inii:.:. `:i:.``:*: ..:` ` ` `,##i;,.`i` `:i;` :*.``
|
||||||
|
``+``.;#;:`.`;i:,+:,,:,,n*++:`..` .,++#,n,.` `..+,`*:`.:,.` ` ,,;+n*i:.`.i``;nnn*`:*,.`
|
||||||
|
`*:` .,#*ii+, `..,,`:.,,z:,#;.` ;:ni+:nz;.``i;z;i.:*i;.` `.;:#*;*,`#i:. `* `nMi*+`;+,.`
|
||||||
|
`:i,:` ``` ` ``;:,.,:,x:**i.. *:zi*;i+i:. ::+#.,+#;` `;,;;#i:` ;+,.` `# `+Mnn*`#i,.`` ` `
|
||||||
|
` `.i+ii,` ` ` `.;i#*n+zz*+,*i*.. `+:*ii;+ni.,,:i*,i#+::, `::;*n*zi` `,*,` `,*..*+*.*+i,` ` ` ` ```:;**;,`` ``
|
||||||
|
```.i#;;i;i,:i*z**:;ii;;#n:+i,:, .++;+;i+M#i;:ini,xi*;..,;:##++;z*`` ..#,.` `.*+;.,*z#i:.````` ``i:. ,;+.` `
|
||||||
|
` `.,;#+;;:ii;ii*znz++zz,#**i;+:,;*n;+;i+zi**#+#`inn;+*:;,n#*;*,xi. ` .*i,` ``,:*z#*ii:.`` ```.;*,```` `:*``
|
||||||
|
` `i++++#znn+++###+***i;;;i+*z:#+z,:#;++:ni#nnz+zx*`+nMnx:ii+z*###:x;.```,,+,.:i++#zz*;i:.`` .:*i;i:*.```,,```z..`
|
||||||
|
,i*.` `.:++#*;;;::,,,.,:i*W:M+,i:i*iz#;+M@Mz@#M:+WznWnz#:x@@z#;zx#,.,,;.n+i::::,.,;+;.``:*i.`:i;**``.#xz#` :*.`
|
||||||
|
` ;+,. ` `````i:*+:,,....```i;@+;ii:++:+Wn#;n@@xzzx#i;,.,;+M+zWxW#;M*i;;:;+.*;*;#****;;,;i,i;i` ``:ni``;xn:z,`.+,``
|
||||||
|
.;i,` `..` ` ``;,,*i,` ` ` `;i@@,zxWii#z+WWW#,. ``,` `` `:i+x*:zn*i:i;*#:##;:,::,```,`:#i+.```.,+@; `:nxnz, .z,``
|
||||||
|
`,i;` ..,;;,..`. `` ,;;i*;.`` `;z##+,xznzzMW@z:` ` . `.``.;M#zx+i:i*#.n+::,,:` ``++,``.*:;i#;``.:zz;``:#:``
|
||||||
|
i;,`;:izn#zzzi,:.`` ``:,;****izn@@W*;MxMMn:,` ` .,;:,i*:;;,:,` ` ``#WxMnWzz#.ni;:.`` ```` `,i.,;:;iiii;``````` `zi,.
|
||||||
|
.**`*.+z*ii;ii+#*:#i`` ``:.`,**+*#MMzi:+xi``. ```:;+nnMnx#M+#**:`` ``:xWx@nM:iz:::,:.`` `*i;:##+++#n````````;z;,``
|
||||||
|
`` ``**z#;+#i;:,,,::;*#:+#i``` `;:+#+#Wx#*: ` `.:,*zMMxxnnzznnxMWz*;,`` .MMWMz,nW++nn#ii;: ` *;;n*i;:;;i+. .ini:.`
|
||||||
|
`` `*+MMx;ni:,.```..,:i+#i#xi*:` .;;*Wzi.`.. ` ,**zzi;i*++**##*:;nWx#i,` `iWxn;*MzzzMi,,;,,, :+z*;:,..,,*z,,,+z#i;,.`
|
||||||
|
`` `.*zi;+xW*#;,.` ```,;in*i#+*:,..;;*##*``` . .`;;ixn+i+i` ``` ``;+;#+zWz:` `:W#,WMMzz,:x@nz#,.` :x*;:.````.,:+z#+ii;,.`
|
||||||
|
`` :*:` ```**+;.` ` .i+***+*#:i#zxi+nxz@` ``` `;z*MM,zi:` ` ``*``` ,i**@xz;;.,,#+MMM#.;W@@x*#;. `i+i:.`` `.,:;i;::.``
|
||||||
|
``,* `` ` ,+:` ,ii,;i*+#MxW#i;*zxxn@# ` .ii;#z#x*x: `` .zM+` ` *+:z@M**. :MWx+inM#W#n@M+: ` zi;.` ```...,.``
|
||||||
|
``*` ``` ``z:` i;. ``:**+zWxW@Wxz*:;n+ ;;M,nWz#W:` ``` #Mnx,` ` `;M*;#+#;.`:;#W@+i+###WWz*```#;,`` ` ````````
|
||||||
|
:; ``,,.` +;.` `:*` ```ii#;#xn####*x@@Mz `.iiMn.*MW;,` ` ` ;Mxznn. ` `.;Wz.M@i:,,`.x##*@zzW#@x, .,#;,` ``` `
|
||||||
|
::``+#z#, ;*.` +i` `**:*+M+x##@#x@MM#n``i+#@i+M@*:,. ```znnznxi`` `,;@z:x@+,,.`z+MzxWn*##ni..+:+,.:**+; ```
|
||||||
|
;,`;xx.+#` ,*.. ,*:` ``;#n#*++;zMnzWxz+i;:,+zW#+znW:,,, ``;nnnxnnn`` ,ixzM:W#zi: *W*i##Wx#n#,,`:i*+*:` .+:``
|
||||||
|
;:`;nx,+z. ,*,.`,++ ```z+***+zzi,;**i::*;;iixx*z+@#`;:.` ``+xnnMnzM``` ` .:#+n#;+i*``,n*iz#;;*nz;:`;M#+, `#.``
|
||||||
|
.:;``+MMn* ` i+:.``++;.`.*+;::;*iz#:.``,..;.i+i,*zMx:;.:.` ``znnx@xnx.`````.;,Mixi#@*,.,i#:`,i#*n+#,:;*#` ` ``` `+.`
|
||||||
|
`.#` .:ii`` `zi,``.iin+;;;*+,,,;+;zWxz*#:,`,#xn;xi@#+,``.` `MnnM@nnx` ````,:;xin+;*+:`:+` :+MiMWin:+#+;` ``.:. `i:``
|
||||||
|
.,z;``` ``:n*;.```,+innnM+,*+*i*x*;zxx*::`*+W:#ix@+,.:..`` `xxnMx;nx` `` .,i*M+M+:xW+;n;+xx:#M@z+#.#M` `+nz#``;*.`
|
||||||
|
`,;*+:.,*z+*i:.```.:+i@#,` `,#MWz#+*:i.,,;;;i#xM,,,;..` ``zxnM,:nn `` ;#i:Wzz*,#z+:,+#z:++#@#z++`z. ,xM*#:`;*,`
|
||||||
|
````,:i+##*ii;,.`` `.;++` ``` :@Wn#*i;:`,:i*,;i*#*;*,.,:.` *Mxxi#x+`````.i,,iMzii`:;;*;*z#z:*ix#z*i#,,` ;nMin:`;+,.
|
||||||
|
```.::;;;;:,.. ` ` `:#.` ````` WWzM++;ii;i+:.:zi#n*+##n+i:.;xWM@MMz:,,;iiz+nMnz;i*xW@n#;::i**#MM##i*z,. ,xnnz``;+,.
|
||||||
|
...,..``` `;:`` :nxx* xWn;*+@#xnn;.,#z+n+ii*+*;inzzi,..`.,:;:..:+x@z:;i,*nznznzznzz##nWW*zzz+.``.;i.` +i,`
|
||||||
|
``````` `;,` ,zn#ix. xW+;:#n*+W*,:;,+:;i;::,,:.`,.`.`,i;i##ii;*,.*i,.,::++#i+Mxz#zW;*#WMx###;```````:z;,`
|
||||||
|
`i,``:nx+*n.`xi+;#i*##. .`i, ``.:;;,,` ` `,`,;::,:..`` , ` ,``,*ii.,#z;+***n@Mz*+++,````*zi:``
|
||||||
|
`;:``:znMx#`,i+zi;i:;:````:. ```....,,:` ,;i,` ` `` ` ;.```iii.;:::z#+n@x*+;++zz#z##*:,``
|
||||||
|
`:i `*#z+,`#+n**#.**.,*`,, `` ` `` .,:*+nn,.` ` `:z` `i..ii*;;;*+zn*#n#*:;iii*ii;,```
|
||||||
|
.*: `..``+++;i+`+zi,*,`:` ` ` ``` `..,,*+*;:.` .```,zzxi :# .n;ii;i,+##x+i#;,::;::,,.`
|
||||||
|
..++::,,*M##.ii.`#M::i.., `i;i:;:. `` ` ` ` ````` ` ``` `i*` ;:.n:+,:*`;nx++n+i,.....```
|
||||||
|
`.:i++zzWn;;.i:..n#:.:`.` ::;.```` `..` ```. .`` ` ,` ``+ ` .`.:#,+::,i;nn+*;+;.````
|
||||||
|
``.,:;#xWx;* ::`.+x;,.` `` ` ., .,`. .. ` .. `+.` `:,`*@,z.;;z`nx+;:,..`
|
||||||
|
``,,+xxW#;.:+: *nz,*.,`,`. ` ,` ;ni .:,`:i;.`` :````#` ``,:`xn,+:;**`nni;,.``
|
||||||
|
```*zzxnMM:;`iM..+@:.,: ,;..;, ,.,#* iMx;zMxnzn+;:...: ::i;`; ``,nMi*`i.#*.n@;+,``
|
||||||
|
`;nz#zM*Mi;,.n#`+@,` :`.i:;`.;i+:+ni ix, .xMMi``x#z+nz;.`iii*.,``ni:,i,,*: z#n*;;;`
|
||||||
|
`;*;i++n,z+:: ;x:*W:` :#:;:.;xzx+ :#;`*M, `#@#; W@ xzzM#*:nz*i,+z;#x:,*:*`,Mx#M+:``
|
||||||
|
..,:;++,+ii+ .z+,M,;:`;i:inz#:M+`,#i iW, :@##; #x `@#@x`:+;;#zxz*nx:`#.+.`.:#nzi,.`
|
||||||
|
``.,*+:+i#;in;:x +,,*z+:+x:.i`nz i@n ;@; ###@; .#z :##@n `Mn*zn*niW::z:i#.,;.,xi;,`
|
||||||
|
` ``:;,x:.:n#ix,#,,#*nn*;*#:`# zz`+#@ ,@i n##@; :@z ##@#z `n*#xx#z#z.n,`z#,*iz`++:,`
|
||||||
|
` :;i*,:` :nx:*.ni**z#;+i`z`+`#W n##, @+`z###; i#*.x#### ii+*MM#:+:`i, in:i;*i,#:.`
|
||||||
|
` `i.;+nnn*`:M**,*#*W+Mz,+`*;i,*#`n### Mz`n###i z#;;@@##i`z:;xWni:#.:,:,i.``..*:i;.``
|
||||||
|
` `;::*iii+z#,*M::.n;xz@+z;i.#:ii#;+#@W;+M`n###*.xM,n###M::i;iMMz,**`i;i,`:+#+:.;:*;``
|
||||||
|
`***+n#M+n;z,*:*`**+z@+Wi#.+:z:@#i####i#`n###*:Wn.@##@n`z+:MMM+:+,:+*:`;Mx+;;::,,+.``
|
||||||
|
` `,:i;#x.zni+:+,,.#.zM+xW*n.n#:xx:####:x:z###+i#*+##@#;*+;n#xx,:z`#;# ,M+,n#+;:,`*:.`
|
||||||
|
`.,,;;+,n#z,.*.; *`*xzx#WWi*z#n@;##@##niz###++#:M##@W:Mn#@#n#.;z.*,#.in`+#n+z+++:i.`
|
||||||
|
`````*iz@MW;.,;,::,i##x###@;x@@@zn##@Wnnz###+Mzz###@*WW+@##n,;;z,+.z.#,z#x;n*i**n;,`
|
||||||
|
````+#*nW#x,`z`:;,;;#M###@x+##@@####@@x####z@n#@##@W@x@##@n`;iW;#;+;:#Wx;x+#;::n:.`
|
||||||
|
`.,+nx*M#;x.:#i.*;@####@xM@########@n###W####@#########W+,*Wi#x,,#+i,*#i:,..,..`
|
||||||
|
.,i;MW+M:x.+;n;i;####@#@@@##@###@##W#######@#####@#####+*#M#;x:#,:i;z*;:.``.```
|
||||||
|
``,**##n;W:;+zi;;####@#####@#@###@##########@###@##@###*ziMM*+x;.#z#*;,.`` `
|
||||||
|
`.:*MWx;x#;z+i*#######################@###@##@#####@@#iMizni*zi.+i#;:.`
|
||||||
|
``.+nMM:xx:#++#MW@######@#######################@@W##@izi*#nzn, ;n*:..`
|
||||||
|
``*nMM,zM:@*z#W+@##@##M@################@###@###W*##@n*i;+#M*,.,,#:. `
|
||||||
|
`**Mxi*x+n##zxi#@#@zW##########W@##########z@###*+##xzii#*@::Mz+;*.`
|
||||||
|
`nx+zn;+z+z,xn,####M########@W##W####@#WW##*@@###,W#@++#Mzni;+z+*;.``
|
||||||
|
`,;+xin:iWM,n+,W#@:#iz#######W#;W######x###ixxx#M`n#@z:znxn,#x#*;,.`
|
||||||
|
.,#+z*:;x#;+;,#@x,W:n######i#W:W######x;##innn#@ #iz#`W@nz:W*+;:.``
|
||||||
|
`.ii#*,n+#M,#i;@n.x.n#####W:#z,M######M.##;#x*##`i*n.+#zxiM##i:.``
|
||||||
|
``,+#:.+;@@#,*#nn`n`z#####n`@#.x######M,W#iix,##`iin.xWxMxni#;,`
|
||||||
|
``.:+,:::W#@+.+@# n`*#####n #*`z######M,n#i,x.@n i#z;MWWz++#i:.`
|
||||||
|
` `.+.;`+@###,iMi`n`i##@##z #i z####@#M,z#:`M,M+ i#.;WMMxxi#;,`
|
||||||
|
` `;iiinM##@M+,#zin`,x@###* @: *######M.+@``x.n+;M`.+@#M#W;+:,
|
||||||
|
`.;*#zzW##@#ii,@Mn` +@###:`x: :######n`#z `x`#iM+`i##WWW;+i:.
|
||||||
|
;i*+#x+M#W@z*i:#n, ;xx#@. #: .#####@# #i` Wn*#`,,i#@W@z**;.`
|
||||||
|
.,:;*+zz#W+zi#.`:zn+..;x +; `######,`++ ,+#i`;;#@@@@Mi#i:.`
|
||||||
|
``.,,:;++#W@#**+,` *n@#i#,`+i .xnnn,#.`*n*+*+ :*M@M@#@zn*;,``
|
||||||
|
``...,:*#+#@*nzi; .,;ix+i+zx#+.:,:i,##*+nn*i`i+#x@###*#i:.
|
||||||
|
` ```.,;z#MWi@#+zi,.``iz*Mn;;M@#@#@;:+x*..:i:W###xi+,;i,`
|
||||||
|
`.,;##zx+@x+++,;.,;,;+W@#M#*i*#zx: :;*ix+zW+*zz*z;.`
|
||||||
|
``.:iz*xn@#zii+i+:,.`::.``.````:`:+#nW#nniz#**i;,.`
|
||||||
|
`.,*ziM@@#xz:::ix,i**;i;zz++i;;#+n#Mn###*i;::,.`
|
||||||
|
`.,;xi#xxW#n,;,,*#+*+*i+;i,.i#z@#M**z*i;:,.```
|
||||||
|
``:#++##+#z;xz:zM##Mz#n+*, x#@@M*##ii:,.``
|
||||||
|
`,:;;*+zM;+#zz##++z@##+,;@@x++z*;:,.``
|
||||||
|
``,,,:;;zizz**#n@#@####.#z#++z*;:.``
|
||||||
|
```...,#;#+zznnzzz#++z:z#+#i*;,.``
|
||||||
|
``,*;#**iiiiiiii;+;+ii*+;,.
|
||||||
|
``*+;::::,,,,,,,i;*;::,,.`
|
||||||
|
`.,,,..........i:i,,...`
|
||||||
|
```...`` `+,*,.`````
|
||||||
|
``` `;,+,.``
|
||||||
|
` .ii+;.`
|
||||||
|
`**z:.`
|
||||||
|
``:::.`
|
||||||
|
..,.``
|
||||||
|
``````
|
||||||
|
` ```
|
414
src/main/resources/regions.json
Normal file
414
src/main/resources/regions.json
Normal file
@ -0,0 +1,414 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"code": "AL",
|
||||||
|
"country": "Albania"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "AD",
|
||||||
|
"country": "Andorra"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "AR",
|
||||||
|
"country": "Argentina"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "AM",
|
||||||
|
"country": "Armenia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "AU",
|
||||||
|
"country": "Australia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "AT",
|
||||||
|
"country": "Austria"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "AZ",
|
||||||
|
"country": "Azerbaijan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BH",
|
||||||
|
"country": "Bahrain"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BD",
|
||||||
|
"country": "Bangladesh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BY",
|
||||||
|
"country": "Belarus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BE",
|
||||||
|
"country": "Belgium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BA",
|
||||||
|
"country": "Bosnia and Herzegovina"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BR",
|
||||||
|
"country": "Brazil"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BG",
|
||||||
|
"country": "Bulgaria"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "KH",
|
||||||
|
"country": "Cambodia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "CA",
|
||||||
|
"country": "Canada"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "CL",
|
||||||
|
"country": "Chile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "CN",
|
||||||
|
"country": "China"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "CO",
|
||||||
|
"country": "Colombia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "CR",
|
||||||
|
"country": "Costa Rica"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "HR",
|
||||||
|
"country": "Croatia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "CW",
|
||||||
|
"country": "Curacao"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "CY",
|
||||||
|
"country": "Cyprus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "CZ",
|
||||||
|
"country": "Czech Republic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "DK",
|
||||||
|
"country": "Denmark"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "DO",
|
||||||
|
"country": "Dominican Republic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EC",
|
||||||
|
"country": "Ecuador"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EG",
|
||||||
|
"country": "Egypt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "SV",
|
||||||
|
"country": "El Salvador"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EE",
|
||||||
|
"country": "Estonia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "FI",
|
||||||
|
"country": "Finland"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "FR",
|
||||||
|
"country": "France"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "GE",
|
||||||
|
"country": "Georgia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "DE",
|
||||||
|
"country": "Germany"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "GR",
|
||||||
|
"country": "Greece"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "GT",
|
||||||
|
"country": "Guatemala"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "HN",
|
||||||
|
"country": "Honduras"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "HK",
|
||||||
|
"country": "Hong Kong"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "HU",
|
||||||
|
"country": "Hungary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "IS",
|
||||||
|
"country": "Iceland"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "IN",
|
||||||
|
"country": "India"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ID",
|
||||||
|
"country": "Indonesia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "IE",
|
||||||
|
"country": "Ireland"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "IM",
|
||||||
|
"country": "Isle of Man"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "IL",
|
||||||
|
"country": "Israel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "IT",
|
||||||
|
"country": "Italy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "JM",
|
||||||
|
"country": "Jamaica"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "JP",
|
||||||
|
"country": "Japan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "JO",
|
||||||
|
"country": "Jordan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "KZ",
|
||||||
|
"country": "Kazakhstan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "KE",
|
||||||
|
"country": "Kenya"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "KR",
|
||||||
|
"country": "Korea"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "KW",
|
||||||
|
"country": "Kuwait"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "KG",
|
||||||
|
"country": "Kyrgyzstan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "LV",
|
||||||
|
"country": "Latvia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "LB",
|
||||||
|
"country": "Lebanon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "LT",
|
||||||
|
"country": "Lithuania"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "LU",
|
||||||
|
"country": "Luxembourg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "MY",
|
||||||
|
"country": "Malaysia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "MT",
|
||||||
|
"country": "Malta"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "MX",
|
||||||
|
"country": "Mexico"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "MD",
|
||||||
|
"country": "Moldova"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "MN",
|
||||||
|
"country": "Mongolia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "MA",
|
||||||
|
"country": "Morocco"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "NL",
|
||||||
|
"country": "Netherlands"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "NZ",
|
||||||
|
"country": "New Zealand"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "NI",
|
||||||
|
"country": "Nicaragua"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "NG",
|
||||||
|
"country": "Nigeria"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "NO",
|
||||||
|
"country": "Norway"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "OM",
|
||||||
|
"country": "Oman"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "PK",
|
||||||
|
"country": "Pakistan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "PA",
|
||||||
|
"country": "Panama"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "PY",
|
||||||
|
"country": "Paraguay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "PE",
|
||||||
|
"country": "Peru"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "PH",
|
||||||
|
"country": "Philippines"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "PL",
|
||||||
|
"country": "Poland"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "PT",
|
||||||
|
"country": "Portugal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "PR",
|
||||||
|
"country": "Puerto Rico"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "QA",
|
||||||
|
"country": "Qatar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "RO",
|
||||||
|
"country": "Romania"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "RU",
|
||||||
|
"country": "Russian Federation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "SA",
|
||||||
|
"country": "Saudi Arabia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "RS",
|
||||||
|
"country": "Serbia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "SG",
|
||||||
|
"country": "Singapore"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "SK",
|
||||||
|
"country": "Slovakia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "SI",
|
||||||
|
"country": "Slovenia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ZA",
|
||||||
|
"country": "South Africa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ES",
|
||||||
|
"country": "Spain"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "SE",
|
||||||
|
"country": "Sweden"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "CH",
|
||||||
|
"country": "Switzerland"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "TW",
|
||||||
|
"country": "Taiwan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "TZ",
|
||||||
|
"country": "Tanzania"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "TH",
|
||||||
|
"country": "Thailand"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "TN",
|
||||||
|
"country": "Tunisia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "TR",
|
||||||
|
"country": "Turkey"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "UA",
|
||||||
|
"country": "Ukraine"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "AE",
|
||||||
|
"country": "United Arab Emirates"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "GB",
|
||||||
|
"country": "United Kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "US",
|
||||||
|
"country": "United States"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "UY",
|
||||||
|
"country": "Uruguay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "UZ",
|
||||||
|
"country": "Uzbekistan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "VE",
|
||||||
|
"country": "Venezuela"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "VN",
|
||||||
|
"country": "Vietnam"
|
||||||
|
}
|
||||||
|
]
|
193
src/test/java/dev/vality/beholder/BeholderFullTest.java
Normal file
193
src/test/java/dev/vality/beholder/BeholderFullTest.java
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
package dev.vality.beholder;
|
||||||
|
|
||||||
|
import dev.vality.beholder.config.properties.PaymentsProperties;
|
||||||
|
import dev.vality.beholder.config.properties.SeleniumProperties;
|
||||||
|
import dev.vality.beholder.security.KeycloakService;
|
||||||
|
import dev.vality.beholder.service.BeholderService;
|
||||||
|
import dev.vality.beholder.service.SeleniumService;
|
||||||
|
import dev.vality.beholder.testutil.ResponseUtil;
|
||||||
|
import dev.vality.swag.payments.ApiClient;
|
||||||
|
import dev.vality.swag.payments.api.ClaimsApi;
|
||||||
|
import dev.vality.swag.payments.api.InvoicesApi;
|
||||||
|
import dev.vality.swag.payments.api.PartiesApi;
|
||||||
|
import dev.vality.swag.payments.api.ShopsApi;
|
||||||
|
import dev.vality.swag.payments.model.Party;
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.actuate.metrics.AutoConfigureMetrics;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.web.client.HttpClientErrorException;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
|
||||||
|
@AutoConfigureMetrics
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
@SpringBootTest(
|
||||||
|
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||||
|
properties = {"beholder.cron=-", //disables scheduled execution
|
||||||
|
"payments.request.shop-id=test",
|
||||||
|
"payments.request.create-shop-if-not-found=true",
|
||||||
|
"payments.request.payment-institution-id=1",
|
||||||
|
"payments.request.category-id=1",
|
||||||
|
"management.server.port="})
|
||||||
|
public class BeholderFullTest {
|
||||||
|
|
||||||
|
public static final String TEST_USER_TOKEN = "test_token";
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private KeycloakService keycloakService;
|
||||||
|
@MockBean
|
||||||
|
private ApiClient apiClient;
|
||||||
|
@MockBean
|
||||||
|
private PartiesApi partiesApi;
|
||||||
|
@MockBean
|
||||||
|
private ShopsApi shopsApi;
|
||||||
|
@MockBean
|
||||||
|
private InvoicesApi invoicesApi;
|
||||||
|
@MockBean
|
||||||
|
private ClaimsApi claimsApi;
|
||||||
|
@MockBean
|
||||||
|
private SeleniumService seleniumService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public BeholderService beholderService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SeleniumProperties seleniumProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public PaymentsProperties paymentsProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
private AutoCloseable mocks;
|
||||||
|
|
||||||
|
private Object[] preparedMocks;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void init() {
|
||||||
|
mocks = MockitoAnnotations.openMocks(this);
|
||||||
|
preparedMocks = new Object[] {keycloakService, apiClient, partiesApi, shopsApi, invoicesApi, claimsApi,
|
||||||
|
seleniumService};
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void clean() throws Exception {
|
||||||
|
verifyNoMoreInteractions(preparedMocks);
|
||||||
|
mocks.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void beholdWithExistingShop() throws Exception {
|
||||||
|
Party party = ResponseUtil.getMyPartyResponse();
|
||||||
|
String shopId = paymentsProperties.getRequest().getShopId();
|
||||||
|
|
||||||
|
when(keycloakService.getUserToken()).thenReturn(TEST_USER_TOKEN);
|
||||||
|
when(partiesApi.getMyParty(anyString(), anyString())).thenReturn(party);
|
||||||
|
when(shopsApi.getShopByIDForParty(anyString(), eq(shopId),
|
||||||
|
eq(party.getId()), anyString()))
|
||||||
|
.thenReturn(ResponseUtil.getShopResponse(shopId));
|
||||||
|
when(invoicesApi.createInvoice(anyString(), any(), anyString()))
|
||||||
|
.thenReturn(ResponseUtil.getInvoiceAndToken());
|
||||||
|
when(seleniumService.executePaymentRequest(any(), any()))
|
||||||
|
.thenReturn(ResponseUtil.getFormDataResponse());
|
||||||
|
|
||||||
|
beholderService.behold();
|
||||||
|
|
||||||
|
|
||||||
|
verify(keycloakService, times(1)).getUserToken();
|
||||||
|
verify(apiClient, times(1)).setApiKey(TEST_USER_TOKEN);
|
||||||
|
verify(partiesApi, times(1)).getMyParty(anyString(), anyString());
|
||||||
|
verify(shopsApi, times(1)).getShopByIDForParty(anyString(), eq(shopId),
|
||||||
|
eq(party.getId()), anyString());
|
||||||
|
verify(invoicesApi, times(1)).createInvoice(anyString(), any(), anyString());
|
||||||
|
verify(seleniumService, times(1)).executePaymentRequest(any(), any());
|
||||||
|
|
||||||
|
|
||||||
|
var mvcResult = mockMvc.perform(get("/actuator/prometheus"))
|
||||||
|
.andReturn();
|
||||||
|
String prometheusResponse = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
List<String> metrics = Arrays.stream(prometheusResponse.split("\n"))
|
||||||
|
.filter(row -> row.startsWith("beholder_")).collect(Collectors.toList());
|
||||||
|
Assertions.assertFalse(metrics.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void beholdWithShopCreation() throws Exception {
|
||||||
|
when(keycloakService.getUserToken()).thenReturn(TEST_USER_TOKEN);
|
||||||
|
Party party = ResponseUtil.getMyPartyResponse();
|
||||||
|
when(partiesApi.getMyParty(anyString(), anyString())).thenReturn(party);
|
||||||
|
String shopId = paymentsProperties.getRequest().getShopId();
|
||||||
|
when(shopsApi.getShopByIDForParty(anyString(), eq(shopId),
|
||||||
|
eq(party.getId()), anyString()))
|
||||||
|
.thenThrow(new HttpClientErrorException(HttpStatus.NOT_FOUND));
|
||||||
|
when(claimsApi.createClaim(anyString(), any(), anyString()))
|
||||||
|
.thenReturn(ResponseUtil.getClaim());
|
||||||
|
when(invoicesApi.createInvoice(anyString(), any(), anyString()))
|
||||||
|
.thenReturn(ResponseUtil.getInvoiceAndToken());
|
||||||
|
when(seleniumService.executePaymentRequest(any(), any()))
|
||||||
|
.thenReturn(ResponseUtil.getFormDataResponse());
|
||||||
|
|
||||||
|
beholderService.behold();
|
||||||
|
|
||||||
|
|
||||||
|
verify(keycloakService, times(1)).getUserToken();
|
||||||
|
verify(apiClient, times(1)).setApiKey(TEST_USER_TOKEN);
|
||||||
|
verify(partiesApi, times(1)).getMyParty(anyString(), anyString());
|
||||||
|
verify(shopsApi, times(1)).getShopByIDForParty(anyString(), eq(shopId),
|
||||||
|
eq(party.getId()), anyString());
|
||||||
|
verify(claimsApi, times(1)).createClaim(anyString(), any(), anyString());
|
||||||
|
verify(invoicesApi, times(1)).createInvoice(anyString(), any(), anyString());
|
||||||
|
verify(seleniumService, times(1)).executePaymentRequest(any(), any());
|
||||||
|
|
||||||
|
|
||||||
|
var mvcResult = mockMvc.perform(get("/actuator/prometheus"))
|
||||||
|
.andReturn();
|
||||||
|
String prometheusResponse = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
List<String> metrics = Arrays.stream(prometheusResponse.split("\n"))
|
||||||
|
.filter(row -> row.startsWith("beholder_")).collect(Collectors.toList());
|
||||||
|
Assertions.assertFalse(metrics.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void beholdWithGetShopError() throws Exception {
|
||||||
|
when(keycloakService.getUserToken()).thenReturn(TEST_USER_TOKEN);
|
||||||
|
Party party = ResponseUtil.getMyPartyResponse();
|
||||||
|
when(partiesApi.getMyParty(anyString(), anyString())).thenReturn(party);
|
||||||
|
String shopId = paymentsProperties.getRequest().getShopId();
|
||||||
|
when(shopsApi.getShopByIDForParty(anyString(), eq(shopId),
|
||||||
|
eq(party.getId()), anyString()))
|
||||||
|
.thenThrow(new HttpClientErrorException(HttpStatus.I_AM_A_TEAPOT));
|
||||||
|
|
||||||
|
beholderService.behold();
|
||||||
|
|
||||||
|
|
||||||
|
verify(keycloakService, times(1)).getUserToken();
|
||||||
|
verify(apiClient, times(1)).setApiKey(TEST_USER_TOKEN);
|
||||||
|
verify(partiesApi, times(1)).getMyParty(anyString(), anyString());
|
||||||
|
verify(shopsApi, times(1)).getShopByIDForParty(anyString(), eq(shopId),
|
||||||
|
eq(party.getId()), anyString());
|
||||||
|
|
||||||
|
var mvcResult = mockMvc.perform(get("/actuator/prometheus"))
|
||||||
|
.andReturn();
|
||||||
|
String prometheusResponse = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
List<String> metrics = Arrays.stream(prometheusResponse.split("\n"))
|
||||||
|
.filter(row -> row.startsWith("beholder_")).collect(Collectors.toList());
|
||||||
|
Assertions.assertFalse(metrics.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
111
src/test/java/dev/vality/beholder/IntegrationTest.java
Normal file
111
src/test/java/dev/vality/beholder/IntegrationTest.java
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package dev.vality.beholder;
|
||||||
|
|
||||||
|
import dev.vality.beholder.config.properties.SeleniumProperties;
|
||||||
|
import dev.vality.beholder.security.KeycloakService;
|
||||||
|
import dev.vality.beholder.service.BeholderService;
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.actuate.metrics.AutoConfigureMetrics;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||||
|
import org.springframework.test.context.DynamicPropertySource;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.testcontainers.containers.GenericContainer;
|
||||||
|
import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.utility.DockerImageName;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static dev.vality.beholder.testutil.SystemUtil.isArmArchitecture;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
|
||||||
|
@Disabled("Used only for local testing")
|
||||||
|
@AutoConfigureMetrics
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
@SpringBootTest(
|
||||||
|
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||||
|
properties = {"beholder.cron=-", //disables scheduled execution
|
||||||
|
"payments.api-url=https://api.test.com",
|
||||||
|
"payments.form-url=https://checkout.test.com/checkout.html",
|
||||||
|
"payments.request.shop-id=test", "management.server.port="})
|
||||||
|
public class IntegrationTest {
|
||||||
|
|
||||||
|
public static final String TEST_USER_TOKEN = "test_token";
|
||||||
|
|
||||||
|
public static final String SELENIUM_IMAGE_NAME =
|
||||||
|
(isArmArchitecture() ? "seleniarm" : "selenium") + "/standalone-chromium";
|
||||||
|
|
||||||
|
public static final String SELENIUM_IMAGE_TAG = "101.0";
|
||||||
|
|
||||||
|
@Container
|
||||||
|
public static final GenericContainer SELENIUM_CONTAINER = new GenericContainer(DockerImageName
|
||||||
|
.parse(SELENIUM_IMAGE_NAME)
|
||||||
|
.withTag(SELENIUM_IMAGE_TAG))
|
||||||
|
.withExposedPorts(4444)
|
||||||
|
.waitingFor(new HostPortWaitStrategy());
|
||||||
|
|
||||||
|
@DynamicPropertySource
|
||||||
|
static void redisProperties(DynamicPropertyRegistry registry) {
|
||||||
|
Supplier<Object> seleniumUrlSupplier = () -> "http://" + SELENIUM_CONTAINER.getHost();
|
||||||
|
Supplier<Object> seleniumPortSupplier = () -> SELENIUM_CONTAINER.getMappedPort(4444);
|
||||||
|
registry.add("selenium.url", seleniumUrlSupplier);
|
||||||
|
registry.add("selenium.port", seleniumPortSupplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public BeholderService beholderService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private KeycloakService keycloakService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SeleniumProperties seleniumProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void prepare() {
|
||||||
|
SELENIUM_CONTAINER.setPortBindings(List.of("4444:4444"));
|
||||||
|
SELENIUM_CONTAINER.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void cleanUp() {
|
||||||
|
verifyNoMoreInteractions(keycloakService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void stop() {
|
||||||
|
SELENIUM_CONTAINER.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws Exception {
|
||||||
|
when(keycloakService.getUserToken()).thenReturn(TEST_USER_TOKEN);
|
||||||
|
beholderService.behold();
|
||||||
|
verify(keycloakService, times(1)).getUserToken();
|
||||||
|
|
||||||
|
|
||||||
|
var mvcResult = mockMvc.perform(get("/actuator/prometheus"))
|
||||||
|
.andReturn();
|
||||||
|
String prometheusResponse = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
List<String> metrics = Arrays.stream(prometheusResponse.split("\n"))
|
||||||
|
.filter(row -> row.startsWith("beholder_")).collect(Collectors.toList());
|
||||||
|
Assertions.assertFalse(metrics.isEmpty());
|
||||||
|
|
||||||
|
|
||||||
|
System.out.println("Collected metrics: ");
|
||||||
|
metrics.forEach(System.out::println);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package dev.vality.beholder.converter;
|
||||||
|
|
||||||
|
import dev.vality.beholder.exception.BadFormatException;
|
||||||
|
import dev.vality.beholder.model.NetworkLog;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openqa.selenium.logging.LogEntry;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.util.FileCopyUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class LogEntriesToNetworkLogsConverterTest {
|
||||||
|
|
||||||
|
private static final LogEntriesToNetworkLogsConverter CONVERTER = new LogEntriesToNetworkLogsConverter();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void convertValidLogs() throws IOException {
|
||||||
|
String start = getMessage("valid_start_network_log.json");
|
||||||
|
String stop = getMessage("valid_finish_network_log.json");
|
||||||
|
LogEntry startLogEntry = new LogEntry(Level.ALL, Instant.now().toEpochMilli(), start);
|
||||||
|
LogEntry endLogEntry = new LogEntry(Level.ALL, Instant.now().toEpochMilli(), stop);
|
||||||
|
List<NetworkLog> converted = CONVERTER.convert(List.of(startLogEntry, endLogEntry));
|
||||||
|
|
||||||
|
assertNotNull(converted);
|
||||||
|
assertEquals(1, converted.size());
|
||||||
|
NetworkLog networkLog = converted.get(0);
|
||||||
|
assertNotNull(networkLog.getStart());
|
||||||
|
assertNotNull(networkLog.getEnd());
|
||||||
|
assertTrue(networkLog.getStart() < networkLog.getEnd());
|
||||||
|
assertEquals("https://checkout.test.com/v1/fonts/90d16760.woff2", networkLog.getResource());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void convertInvalidLog() throws IOException {
|
||||||
|
String invalid = getMessage("invalid_network_log.json");
|
||||||
|
LogEntry logEntry = new LogEntry(Level.ALL, Instant.now().toEpochMilli(), invalid);
|
||||||
|
assertThrows(BadFormatException.class, () -> CONVERTER.convert(List.of(logEntry)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getMessage(String filePath) throws IOException {
|
||||||
|
var resource = new ClassPathResource(filePath, getClass());
|
||||||
|
return FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package dev.vality.beholder.service;
|
||||||
|
|
||||||
|
import dev.vality.beholder.model.FormDataRequest;
|
||||||
|
import dev.vality.beholder.model.FormDataResponse;
|
||||||
|
import dev.vality.beholder.model.Region;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
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.web.client.HttpClientErrorException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@SpringBootTest(properties = {"beholder.cron=-",
|
||||||
|
"selenium.regions=AL,AD,AR"})
|
||||||
|
class BeholderServiceTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
BeholderService beholderService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
List<Region> regions;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
PaymentsService paymentsService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
SeleniumService seleniumService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
MetricsService metricsService;
|
||||||
|
|
||||||
|
private AutoCloseable mocks;
|
||||||
|
|
||||||
|
private Object[] preparedMocks;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
mocks = MockitoAnnotations.openMocks(this);
|
||||||
|
preparedMocks = new Object[] {paymentsService, seleniumService, metricsService};
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDown() throws Exception {
|
||||||
|
verifyNoMoreInteractions(preparedMocks);
|
||||||
|
mocks.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void behold() {
|
||||||
|
var firstRequest = new FormDataRequest();
|
||||||
|
var secondRequest = new FormDataRequest();
|
||||||
|
when(paymentsService.prepareFormData())
|
||||||
|
.thenReturn(new FormDataRequest())
|
||||||
|
.thenReturn(new FormDataRequest())
|
||||||
|
.thenThrow(HttpClientErrorException.class);
|
||||||
|
|
||||||
|
var firstResponse = FormDataResponse.builder().build();
|
||||||
|
var secondResponse = FormDataResponse.builder().build();
|
||||||
|
when(seleniumService.executePaymentRequest(firstRequest, regions.get(0)))
|
||||||
|
.thenReturn(firstResponse);
|
||||||
|
when(seleniumService.executePaymentRequest(secondRequest, regions.get(1)))
|
||||||
|
.thenReturn(secondResponse);
|
||||||
|
|
||||||
|
beholderService.behold();
|
||||||
|
|
||||||
|
verify(paymentsService, times(3)).prepareFormData();
|
||||||
|
verify(seleniumService, times(2)).executePaymentRequest(any(), any());
|
||||||
|
verify(metricsService, times(1)).updateMetrics(List.of(firstResponse, secondResponse));
|
||||||
|
}
|
||||||
|
}
|
78
src/test/java/dev/vality/beholder/testutil/ResponseUtil.java
Normal file
78
src/test/java/dev/vality/beholder/testutil/ResponseUtil.java
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package dev.vality.beholder.testutil;
|
||||||
|
|
||||||
|
import dev.vality.beholder.model.Browser;
|
||||||
|
import dev.vality.beholder.model.FormDataRequest;
|
||||||
|
import dev.vality.beholder.model.FormDataResponse;
|
||||||
|
import dev.vality.beholder.model.Region;
|
||||||
|
import dev.vality.swag.payments.model.*;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class ResponseUtil {
|
||||||
|
|
||||||
|
public static Party getMyPartyResponse() {
|
||||||
|
Party party = new Party();
|
||||||
|
party.setId(UUID.randomUUID().toString());
|
||||||
|
party.setIsBlocked(false);
|
||||||
|
party.setIsBlocked(false);
|
||||||
|
return party;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Shop getShopResponse(String id) {
|
||||||
|
Shop shop = new Shop();
|
||||||
|
shop.setId(id);
|
||||||
|
shop.setCurrency("RUB");
|
||||||
|
shop.setIsBlocked(false);
|
||||||
|
shop.setIsSuspended(false);
|
||||||
|
return shop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Claim getClaim() {
|
||||||
|
Claim claim = new Claim();
|
||||||
|
claim.setId(0L);
|
||||||
|
claim.setStatus("success");
|
||||||
|
return claim;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InvoiceAndToken getInvoiceAndToken() {
|
||||||
|
InvoiceAndToken invoiceAndToken = new InvoiceAndToken();
|
||||||
|
invoiceAndToken.setInvoice(new Invoice().id("2"));
|
||||||
|
invoiceAndToken.setInvoiceAccessToken(new AccessToken().payload("invoice_test_token"));
|
||||||
|
return invoiceAndToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FormDataResponse getFormDataResponse() {
|
||||||
|
var region = new Region();
|
||||||
|
region.setCode("AM");
|
||||||
|
region.setCountry("Armenia");
|
||||||
|
|
||||||
|
var invoiceAndToken = getInvoiceAndToken();
|
||||||
|
|
||||||
|
FormDataRequest request = new FormDataRequest();
|
||||||
|
request.setInvoiceAccessToken(invoiceAndToken.getInvoiceAccessToken().getPayload());
|
||||||
|
request.setInvoiceId(invoiceAndToken.getInvoice().getId());
|
||||||
|
|
||||||
|
var requestStartAt = (double) Instant.now().minus(10, ChronoUnit.SECONDS).toEpochMilli();
|
||||||
|
var responseStartAt = requestStartAt + 5;
|
||||||
|
var responseEndAt = responseStartAt + 5;
|
||||||
|
|
||||||
|
FormDataResponse.FormPerformance performance = FormDataResponse.FormPerformance.builder()
|
||||||
|
.requestStartAt(requestStartAt)
|
||||||
|
.responseStartAt(responseStartAt)
|
||||||
|
.responseEndAt(responseEndAt).build();
|
||||||
|
|
||||||
|
return FormDataResponse.builder()
|
||||||
|
.region(region)
|
||||||
|
.browser(Browser.CHROME)
|
||||||
|
.request(request)
|
||||||
|
.networkLogs(List.of())
|
||||||
|
.formPerformance(performance)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
src/test/java/dev/vality/beholder/testutil/SystemUtil.java
Normal file
21
src/test/java/dev/vality/beholder/testutil/SystemUtil.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package dev.vality.beholder.testutil;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import oshi.SystemInfo;
|
||||||
|
import oshi.hardware.CentralProcessor;
|
||||||
|
import oshi.hardware.HardwareAbstractionLayer;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class SystemUtil {
|
||||||
|
|
||||||
|
private static final String ARM = "ARM";
|
||||||
|
|
||||||
|
public static boolean isArmArchitecture() {
|
||||||
|
SystemInfo si = new SystemInfo();
|
||||||
|
HardwareAbstractionLayer hal = si.getHardware();
|
||||||
|
CentralProcessor cpu = hal.getProcessor();
|
||||||
|
var cpuId = cpu.getProcessorIdentifier();
|
||||||
|
return cpuId.getVendor().toUpperCase().contains(ARM)
|
||||||
|
|| cpuId.getMicroarchitecture().toUpperCase().contains(ARM);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"msg": {
|
||||||
|
"method": "Network.loadingFinished",
|
||||||
|
"params": {
|
||||||
|
"encodedDataLength": 65958,
|
||||||
|
"requestId": "325.11",
|
||||||
|
"shouldReportCorbBlocking": false,
|
||||||
|
"timestamp": 30884.97731
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webview": "142FDA4010363970DE1DED0689086616"
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"method": "Network.loadingFinished",
|
||||||
|
"params": {
|
||||||
|
"encodedDataLength": 65958,
|
||||||
|
"requestId": "325.11",
|
||||||
|
"shouldReportCorbBlocking": false,
|
||||||
|
"timestamp": 30884.97731
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webview": "142FDA4010363970DE1DED0689086616"
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"method": "Network.requestWillBeSent",
|
||||||
|
"params": {
|
||||||
|
"documentURL": "https://checkout.test.com/v1/checkout.html?test_token=sample_token",
|
||||||
|
"frameId": "142FDA4010363970DE1DED0689086616",
|
||||||
|
"hasUserGesture": false,
|
||||||
|
"initiator": {
|
||||||
|
"type": "parser",
|
||||||
|
"url": "https://checkout.test.com/v1/aeee2b0f52d9e4fffb4e.css"
|
||||||
|
},
|
||||||
|
"loaderId": "A7EB9BE4EDAD0AFA88AB6E16CA26ECAD",
|
||||||
|
"redirectHasExtraInfo": false,
|
||||||
|
"request": {
|
||||||
|
"headers": {
|
||||||
|
"Origin": "https://checkout.test.com",
|
||||||
|
"Referer": "https://checkout.test.com/v1/aeee2b0f52d9e4fffb4e.css",
|
||||||
|
"User-Agent": "Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36",
|
||||||
|
"sec-ch-ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"101\"",
|
||||||
|
"sec-ch-ua-mobile": "?0",
|
||||||
|
"sec-ch-ua-platform": "\"Linux\""
|
||||||
|
},
|
||||||
|
"initialPriority": "VeryHigh",
|
||||||
|
"isSameSite": true,
|
||||||
|
"method": "GET",
|
||||||
|
"mixedContentType": "none",
|
||||||
|
"referrerPolicy": "strict-origin-when-cross-origin",
|
||||||
|
"url": "https://checkout.test.com/v1/fonts/90d16760.woff2"
|
||||||
|
},
|
||||||
|
"requestId": "325.11",
|
||||||
|
"timestamp": 30884.700929,
|
||||||
|
"type": "Font",
|
||||||
|
"wallTime": 1653919988.713246
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webview": "142FDA4010363970DE1DED0689086616"
|
||||||
|
}
|
10
src/test/resources/logback-test.xml
Normal file
10
src/test/resources/logback-test.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||||
|
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||||
|
|
||||||
|
<root level="warn">
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
</root>
|
||||||
|
<logger name="dev.vality.woody" level="ALL"/>
|
||||||
|
</configuration>
|
Loading…
Reference in New Issue
Block a user