Compare commits
257 commits
maven-cach
...
main
Author | SHA1 | Date | |
---|---|---|---|
1e9ab13c97 | |||
58a53fe280 | |||
584ffc85f2 | |||
9621f913e3 | |||
25ea6c556d | |||
7a33e9cdbb | |||
6a3026de70 | |||
8269687a6e | |||
3ae16bfa25 | |||
ee96894e87 | |||
21913626ad | |||
d1acb1a0ee | |||
bc0110cc29 | |||
f591d514ec | |||
331a830c2b | |||
7d2cda5b20 | |||
ca915c4bf5 | |||
cf81524d86 | |||
35d4e29a57 | |||
ed362d84b8 | |||
fc140833d5 | |||
a0939d7729 | |||
2d23835810 | |||
5e8f8fefa8 | |||
f6e85f2c00 | |||
a5e0c2672b | |||
1cc8a39c88 | |||
8aad75a493 | |||
880df82728 | |||
2d1f9a1417 | |||
129fdb768f | |||
6d95a3b123 | |||
9d84bb8c5e | |||
0fd62decaf | |||
33e10b7a40 | |||
4d20a75f42 | |||
4b20f99856 | |||
3bbdf7015a | |||
d7b6286546 | |||
cccd9dde98 | |||
f9734649f2 | |||
e6ec3f57f8 | |||
1f38615a15 | |||
a22c070401 | |||
d5053a3862 | |||
a9e1f3d8fa | |||
5ac5d90f97 | |||
b813ed4347 | |||
6f7b048266 | |||
3081156f9f | |||
c55e317c92 | |||
66c190208b | |||
b56759c1e6 | |||
6ac4bd783e | |||
bbd1d80d6c | |||
1bd06f1857 | |||
acbe6c5ae2 | |||
7c45b3e4ae | |||
2844cff58a | |||
a0006d4455 | |||
291e3b0712 | |||
eca733d2ce | |||
f268c4a27a | |||
db0026b723 | |||
75a778296c | |||
2ceace7ce7 | |||
673f8afbd3 | |||
eb4812a0ea | |||
8b3e49d2d4 | |||
14c693117d | |||
c16419b9d0 | |||
259366e53a | |||
84cbff6e98 | |||
a2b8338ec9 | |||
386f72992a | |||
50061d3a55 | |||
8396157611 | |||
622ddf5cce | |||
009f25f448 | |||
3d1910ab49 | |||
7947dc8df1 | |||
7b32423be1 | |||
393b866057 | |||
ce8a87b1db | |||
00ce66f1b2 | |||
28646cf254 | |||
71efdbfdfd | |||
943717354d | |||
0621b5e4dc | |||
e0a106233d | |||
5f33dc17b6 | |||
cfc84db08d | |||
615028ceae | |||
8c952a7e54 | |||
b43f8864ea | |||
f3ed6bb854 | |||
f83210a82d | |||
b9ee1b0d09 | |||
7560df3a38 | |||
41983e893b | |||
9fa90d05ab | |||
25befbf948 | |||
71c9670bfe | |||
8b58eaa5d8 | |||
cf2ed37c16 | |||
bef4d1c3fe | |||
7c8508015e | |||
934fb65b02 | |||
bfa057551a | |||
d4b606cbc7 | |||
3d2a3e98dd | |||
0d3a73be8d | |||
ef3765299d | |||
0e118f0d7f | |||
d608963610 | |||
edef62f15c | |||
cb2fc9ffab | |||
44b708800b | |||
a24441c8c5 | |||
7a5248769e | |||
d879cfd34d | |||
15b8c34429 | |||
2daa7f2dc5 | |||
7196f143b7 | |||
5d3cd4e402 | |||
6d8820ee94 | |||
dd1adc3940 | |||
08b8ce9b28 | |||
4f420d5163 | |||
530d33ecbc | |||
ee20f00024 | |||
4723a1502f | |||
832fd133c0 | |||
f978eec07a | |||
d0e6cc2802 | |||
032710a0bd | |||
53c63290ad | |||
3edeafc5db | |||
f3c569d07d | |||
6b35b8f7a1 | |||
d6dc1f7627 | |||
15fc85d301 | |||
086a2e3f2a | |||
c477b9e908 | |||
552a3b62ba | |||
7faebe8c9a | |||
2e12ff563e | |||
8ab489c27f | |||
c3ac127221 | |||
40a2ccc201 | |||
5a01d2461b | |||
2dc1794f90 | |||
1e2c4746c1 | |||
8febf96d10 | |||
f15f68b3ed | |||
0add72c330 | |||
732a0ed0b5 | |||
741001627e | |||
42b3bcb38d | |||
c4eef322cf | |||
2563b3699c | |||
3d569f7a35 | |||
18bcf47b57 | |||
2f40dab711 | |||
9e008accfa | |||
a2a67d25be | |||
12b493ae43 | |||
422e1c3479 | |||
59a8c7d5a2 | |||
43bf60469e | |||
5a05e2118a | |||
a3e4cdeffd | |||
17df7e23a3 | |||
e3e6d37335 | |||
7cb23cfda9 | |||
22c2803ada | |||
a30cc1db57 | |||
4aae487289 | |||
c469b5c70c | |||
a624ca234a | |||
ba95e7aab2 | |||
34c2db3c61 | |||
cc8cbeb239 | |||
6125a55868 | |||
b3c77aa3b8 | |||
bb7e222c55 | |||
4273cc0f16 | |||
a23dfa5399 | |||
019b0f8b17 | |||
0fa8453eec | |||
070101f041 | |||
62f1c008d3 | |||
a5edd178da | |||
d122200221 | |||
9f1a89d255 | |||
a8e526cf9f | |||
6b6a46a860 | |||
b1e87a2a44 | |||
a497a4ba06 | |||
81b1c13d8e | |||
5bb53631a3 | |||
d77e1f7909 | |||
c91a9e136b | |||
cb9da7e719 | |||
5168f9c930 | |||
8965706e7a | |||
0d93a2d540 | |||
e5c3fc58ec | |||
bc9577385c | |||
ca5c9519d4 | |||
d2078337eb | |||
01fe45047a | |||
c9995eb309 | |||
9e6dd3dab1 | |||
44137d29e1 | |||
8ca36d0cc0 | |||
0013789f31 | |||
baf4f4c630 | |||
1b57649f2b | |||
b9d4e6f6d3 | |||
fa1578f667 | |||
483c5b1b57 | |||
5b0fca22d0 | |||
9d28933116 | |||
27de665903 | |||
92ee17fb6f | |||
b2d100499c | |||
602c8ec1aa | |||
5d93e7a8cf | |||
fe96666d70 | |||
b16eae1b43 | |||
65d25d045b | |||
7cf9b1e52b | |||
2509843da4 | |||
efb25640dd | |||
1ed8a8a12f | |||
e3abbf4b7f | |||
39e2667a3e | |||
e6b6d5f1e7 | |||
2cdf40e136 | |||
f36d1843f6 | |||
e59c3a2ce9 | |||
9c3289f1ec | |||
6fc943ecbb | |||
a8d43b1bb9 | |||
84deff212a | |||
3454576dcb | |||
8eec3aeaf6 | |||
d404db0e87 | |||
25fe14a5a7 | |||
797c3ed950 | |||
a18c903202 | |||
7ec59eaef4 | |||
1efdbf379d | |||
0c3a30138a | |||
40598dbe87 | |||
e5e100076c |
88 changed files with 2911 additions and 314 deletions
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
mvnw text eol=lf
|
||||
*.xml text eol=lf
|
||||
*.java text diff=java eol=lf
|
|
@ -1,42 +0,0 @@
|
|||
name: Build, test and publish the Quarkus libraries
|
||||
on: [ push, workflow_dispatch ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout the code
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: https://github.com/actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
- name: Configure Maven CI/CD settings
|
||||
uses: https://github.com/s4u/maven-settings-action@v3.1.0
|
||||
with:
|
||||
servers: |
|
||||
[{
|
||||
"id": "kvant",
|
||||
"configuration": {
|
||||
"httpHeaders": {
|
||||
"property": {
|
||||
"name": "Authorization",
|
||||
"value": "token ${{ secrets.PHOENIX_PACKAGE_WRITER_TOKEN }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
||||
- name: Make maven wrapper executable
|
||||
run: chmod +x mvnw
|
||||
|
||||
- name: Build and run tests
|
||||
run: ./mvnw verify
|
||||
|
||||
- name: Upload libs to Gitea Maven Registry
|
||||
if: gitea.ref == 'refs/heads/main'
|
||||
run: ./mvnw deploy -Dmaven.test.skip=true -Dmaven.javadoc.skip=true
|
5
.github/project.yaml
vendored
Normal file
5
.github/project.yaml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: Quarkus Commons
|
||||
release:
|
||||
current-version: "1.0.8"
|
||||
next-version: "1.0.9-SNAPSHOT"
|
||||
|
99
.github/workflows/build.yaml
vendored
Normal file
99
.github/workflows/build.yaml
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+'
|
||||
paths-ignore:
|
||||
- '.gitattributes'
|
||||
- '.gitignore'
|
||||
- '.github/renovate.json5'
|
||||
- '.github/project.yaml'
|
||||
- 'docs/**'
|
||||
- 'README.md'
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
COMMON_MAVEN_OPTS: "-e -B --fae"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout the code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: https://github.com/actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
- name: Generate cache keys
|
||||
id: cache-key
|
||||
run: |
|
||||
CURRENT_BRANCH="${{ github.head_ref || github.ref_name }}"
|
||||
CURRENT_MONTH=$(/bin/date -u "+%Y-%m")
|
||||
CURRENT_DAY=$(/bin/date -u "+%d")
|
||||
ROOT_CACHE_KEY="m2-cache-quarkus-commons"
|
||||
echo "m2-monthly-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}" >> $GITHUB_OUTPUT
|
||||
echo "m2-monthly-branch-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}-${CURRENT_BRANCH}" >> $GITHUB_OUTPUT
|
||||
echo "m2-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}-${CURRENT_BRANCH}-${CURRENT_DAY}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Maven Repository
|
||||
id: cache-maven
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
# The cache is per branch but in case we don't find a branch for a given branch, we will get a cache from another branch.
|
||||
key: ${{ steps.cache-key.outputs.m2-cache-key }}
|
||||
restore-keys: |
|
||||
${{ steps.cache-key.outputs.m2-monthly-branch-cache-key }}
|
||||
${{ steps.cache-key.outputs.m2-monthly-cache-key }}
|
||||
|
||||
- name: Configure Maven CI/CD settings
|
||||
uses: https://github.com/s4u/maven-settings-action@v3.1.0
|
||||
with:
|
||||
servers: |
|
||||
[{
|
||||
"id": "phoenix-oss",
|
||||
"username": "${{ vars.ORG_PACKAGE_WRITER_USERNAME }}",
|
||||
"password": "${{ secrets.ORG_PACKAGE_WRITER_TOKEN }}"
|
||||
}]
|
||||
|
||||
- name: Make maven wrapper executable
|
||||
run: chmod +x mvnw
|
||||
|
||||
- name: Validate tag
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
TAG_NAME="${GITHUB_REF#refs/tags/}"
|
||||
PROJECT_VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)
|
||||
|
||||
if [[ "$PROJECT_VERSION" != "$TAG_NAME" ]]; then
|
||||
echo "::error::pom.xml version '$PROJECT_VERSION' does not match tag '$TAG_NAME'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download dependencies
|
||||
run: ./mvnw $COMMON_MAVEN_OPTS quarkus:go-offline
|
||||
|
||||
- name: Build and run tests
|
||||
run: ./mvnw $COMMON_MAVEN_OPTS verify
|
||||
|
||||
- name: Analyze with Sonar
|
||||
if: vars.SONAR_ENABLED == 'true'
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
|
||||
run: ./mvnw $COMMON_MAVEN_OPTS org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=quarkus-commons -Dsonar.projectName='quarkus-commons' -Dsonar.coverage.jacoco.xmlReportPaths=../**/target/jacoco-report/jacoco.xml
|
||||
|
||||
- name: Publish jars
|
||||
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
|
||||
run: ./mvnw $COMMON_MAVEN_OPTS deploy -Dmaven.test.skip=true -Dmaven.javadoc.skip=true
|
97
.github/workflows/release.yaml
vendored
Normal file
97
.github/workflows/release.yaml
vendored
Normal file
|
@ -0,0 +1,97 @@
|
|||
name: Release the current version
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- '.github/project.yaml'
|
||||
|
||||
env:
|
||||
COMMON_MAVEN_OPTS: "-e -B --fae"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Execute the release
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout the code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Retrieve project metadata
|
||||
uses: https://github.com/radcortez/project-metadata-action@main
|
||||
id: metadata
|
||||
with:
|
||||
metadata-file-path: '.github/project.yaml'
|
||||
local-file: true
|
||||
|
||||
- name: Validate current version
|
||||
if: contains(steps.metadata.outputs.current-version, 'SNAPSHOT')
|
||||
run: |
|
||||
echo '::error::Cannot release a SNAPSHOT version.'
|
||||
exit 1
|
||||
|
||||
- name: Validate next version
|
||||
if: contains(steps.metadata.outputs.next-version, 'SNAPSHOT') == 'false'
|
||||
run: |
|
||||
echo '::error::Next development version should be a SNAPSHOT version.'
|
||||
exit 1
|
||||
|
||||
- name: Set environment variables
|
||||
run: |
|
||||
echo "CURRENT_VERSION=${{steps.metadata.outputs.current-version}}" >> $GITHUB_ENV
|
||||
echo "NEXT_VERSION=${{steps.metadata.outputs.next-version}}" >> $GITHUB_ENV
|
||||
|
||||
- name: Configure SSH and Git
|
||||
env:
|
||||
SSH_DIR: /root/.ssh
|
||||
MAVEN_RELEASE_SSH_KEY: ${{ secrets.MAVEN_RELEASE_SSH_KEY }}
|
||||
run: |
|
||||
mkdir -p ${SSH_DIR}/
|
||||
touch ${SSH_DIR}/known_hosts
|
||||
ssh-keyscan -t rsa -p 2222 git-ssh.kvant.cloud >> ${SSH_DIR}/known_hosts
|
||||
echo "${MAVEN_RELEASE_SSH_KEY}" | base64 -d >> ${SSH_DIR}/id_rsa
|
||||
chmod 600 ${SSH_DIR}/id_rsa
|
||||
git config --global user.name 'maven_release_technical_account'
|
||||
git config --global user.email 'maven-release-bot@phoenix-technologies.ch'
|
||||
git config --global commit.gpgsign true
|
||||
git config --global gpg.format ssh
|
||||
git config --global user.signingkey ${SSH_DIR}/id_rsa
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: https://github.com/actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
- name: Generate cache keys
|
||||
id: cache-key
|
||||
run: |
|
||||
CURRENT_BRANCH="${{ github.ref_name }}"
|
||||
CURRENT_MONTH=$(/bin/date -u "+%Y-%m")
|
||||
CURRENT_DAY=$(/bin/date -u "+%d")
|
||||
ROOT_CACHE_KEY="m2-cache-quarkus-commons"
|
||||
echo "m2-monthly-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}" >> $GITHUB_OUTPUT
|
||||
echo "m2-monthly-branch-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}-${CURRENT_BRANCH}" >> $GITHUB_OUTPUT
|
||||
echo "m2-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}-${CURRENT_BRANCH}-${CURRENT_DAY}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Maven Repository
|
||||
id: cache-maven
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
# The cache is per branch but in case we don't find a branch for a given branch, we will get a cache from another branch.
|
||||
key: ${{ steps.cache-key.outputs.m2-cache-key }}
|
||||
restore-keys: |
|
||||
${{ steps.cache-key.outputs.m2-monthly-branch-cache-key }}
|
||||
${{ steps.cache-key.outputs.m2-monthly-cache-key }}
|
||||
|
||||
- name: Make maven wrapper executable
|
||||
run: chmod +x mvnw
|
||||
|
||||
- name: Download dependencies
|
||||
run: ./mvnw $COMMON_MAVEN_OPTS quarkus:go-offline
|
||||
|
||||
- name: Prepare release
|
||||
run: ./mvnw $COMMON_MAVEN_OPTS release:prepare -DreleaseVersion=${CURRENT_VERSION} -DdevelopmentVersion=${NEXT_VERSION}
|
33
.github/workflows/validate-versions.yaml
vendored
Normal file
33
.github/workflows/validate-versions.yaml
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
name: Validate release versions
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/project.yaml'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout the code
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- name: Retrieve project metadata
|
||||
uses: https://github.com/radcortez/project-metadata-action@main
|
||||
id: metadata
|
||||
with:
|
||||
metadata-file-path: '.github/project.yaml'
|
||||
local-file: true
|
||||
|
||||
- name: Validate current version
|
||||
if: contains(steps.metadata.outputs.current-version, 'SNAPSHOT')
|
||||
run: |
|
||||
echo '::error::Cannot release a SNAPSHOT version.'
|
||||
exit 1
|
||||
|
||||
- name: Validate next version
|
||||
if: contains(steps.metadata.outputs.next-version, 'SNAPSHOT') == false
|
||||
run: |
|
||||
echo '::error::Next development version should be a SNAPSHOT version.'
|
||||
exit 1
|
17
README.md
17
README.md
|
@ -1,14 +1,13 @@
|
|||
Quarkus Commons
|
||||
===============
|
||||
|
||||
[](https://openjdk.org/projects/jdk/21/)
|
||||
[](https://quarkus.io/guides/)
|
||||
[](https://maven.apache.org/)
|
||||
|
||||
<!-- TOC -->
|
||||
* [Quarkus Commons](#quarkus-commons)
|
||||
* [Introduction](#introduction)
|
||||
<!-- TOC -->
|
||||
[](https://openjdk.org/projects/jdk/21/)
|
||||
[](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
|
||||
[](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
|
||||
[](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
|
||||
[](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
|
||||
[](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
|
||||
[](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
|
||||
|
||||
# Introduction
|
||||
|
||||
|
@ -17,9 +16,11 @@ that can be used by Quarkus applications.
|
|||
|
||||
The modules are:
|
||||
|
||||
* `quarkus-audit-tools`
|
||||
* `quarkus-clock-service`
|
||||
* `quarkus-json-service`
|
||||
* `quarkus-message-digest-service`
|
||||
* `quarkus-random-number-generator`
|
||||
* `quarkus-tracing-service`
|
||||
* `quarkus-uuid-generator`
|
||||
|
||||
|
|
122
pom.xml
122
pom.xml
|
@ -1,49 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>ch.phoenixtechnologies.quarkus</groupId>
|
||||
<groupId>ch.phoenix.oss</groupId>
|
||||
<artifactId>quarkus-commons</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<version>1.0.9-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>quarkus-audit-tools</module>
|
||||
<module>quarkus-client-logger</module>
|
||||
<module>quarkus-clock-service</module>
|
||||
<module>quarkus-json-service</module>
|
||||
<module>quarkus-message-digest-service</module>
|
||||
<module>quarkus-random-number-generator</module>
|
||||
<module>quarkus-tracing-service</module>
|
||||
<module>quarkus-uuid-generator</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
||||
<compiler-plugin.version>3.13.0</compiler-plugin.version>
|
||||
<surefire-plugin.version>3.5.2</surefire-plugin.version>
|
||||
<jacoco-plugin.version>0.8.12</jacoco-plugin.version>
|
||||
|
||||
<!-- Quarkus properties -->
|
||||
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
||||
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
|
||||
<quarkus.platform.version>3.16.2</quarkus.platform.version>
|
||||
<quarkus.platform.version>3.25.0</quarkus.platform.version>
|
||||
|
||||
<!-- Plugin versions -->
|
||||
<compiler-plugin.version>3.14.0</compiler-plugin.version>
|
||||
<surefire-plugin.version>3.5.3</surefire-plugin.version>
|
||||
<spotless-plugin.version>2.46.1</spotless-plugin.version>
|
||||
<palantir-java-format.version>2.72.0</palantir-java-format.version>
|
||||
<jacoco-plugin.version>0.8.13</jacoco-plugin.version> <!-- Match with version from Quarkus BOM -->
|
||||
<jandex-plugin.version>3.4.0</jandex-plugin.version>
|
||||
<release-plugin.version>3.1.1</release-plugin.version>
|
||||
<source-plugin.version>3.3.1</source-plugin.version>
|
||||
|
||||
<!-- Build properties -->
|
||||
<maven.compiler.release>21</maven.compiler.release>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<skipITs>true</skipITs>
|
||||
|
||||
<!-- Dependencies -->
|
||||
<java-uuid-generator.version>5.1.0</java-uuid-generator.version>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<assertj-core.version>3.26.3</assertj-core.version>
|
||||
<assertj-core.version>3.27.3</assertj-core.version>
|
||||
</properties>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>kvant</id>
|
||||
<url>https://git.kvant.cloud/api/packages/phoenix/maven</url>
|
||||
<id>phoenix-oss</id>
|
||||
<url>https://git.kvant.cloud/api/packages/phoenix-oss/maven</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>kvant</id>
|
||||
<url>https://git.kvant.cloud/api/packages/phoenix/maven</url>
|
||||
<id>phoenix-oss</id>
|
||||
<url>https://git.kvant.cloud/api/packages/phoenix-oss/maven</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
|
@ -59,11 +69,22 @@
|
|||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:ssh://git@git-ssh.kvant.cloud:2222/phoenix-oss/quarkus-commons.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@git-ssh.kvant.cloud:2222/phoenix-oss/quarkus-commons.git</developerConnection>
|
||||
<url>https://git.kvant.cloud/phoenix-oss/quarkus-commons.git</url>
|
||||
<tag>HEAD</tag>
|
||||
</scm>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-arc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-cyclonedx</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
|
@ -117,7 +138,6 @@
|
|||
<compilerArgs>
|
||||
<arg>-parameters</arg>
|
||||
</compilerArgs>
|
||||
<release>${java.version}</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
@ -132,6 +152,68 @@
|
|||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.diffplug.spotless</groupId>
|
||||
<artifactId>spotless-maven-plugin</artifactId>
|
||||
<version>${spotless-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<phase>compile</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<java>
|
||||
<removeUnusedImports />
|
||||
<palantirJavaFormat>
|
||||
<version>${palantir-java-format.version}</version>
|
||||
<style>PALANTIR</style>
|
||||
<formatJavadoc>false</formatJavadoc>
|
||||
</palantirJavaFormat>
|
||||
</java>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>io.smallrye</groupId>
|
||||
<artifactId>jandex-maven-plugin</artifactId>
|
||||
<version>${jandex-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-index</id>
|
||||
<goals>
|
||||
<goal>jandex</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-release-plugin</artifactId>
|
||||
<version>${release-plugin.version}</version>
|
||||
<configuration>
|
||||
<tagNameFormat>@{project.version}</tagNameFormat>
|
||||
<checkModificationExcludes>mvnw</checkModificationExcludes>
|
||||
<scmReleaseCommitComment>chore: release @{releaseLabel}</scmReleaseCommitComment>
|
||||
<scmDevelopmentCommitComment>chore: prepare for next development iteration</scmDevelopmentCommitComment>
|
||||
<autoVersionSubmodules>true</autoVersionSubmodules>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>${source-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
88
quarkus-audit-tools/pom.xml
Normal file
88
quarkus-audit-tools/pom.xml
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?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>ch.phoenix.oss</groupId>
|
||||
<artifactId>quarkus-commons</artifactId>
|
||||
<version>1.0.9-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>quarkus-audit-tools</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>ch.phoenix.oss</groupId>
|
||||
<artifactId>quarkus-tracing-service</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-hibernate-envers</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-hibernate-orm-panache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jdbc-postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-flyway</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-flyway-postgresql</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-elytron-security-properties-file</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-config-yaml</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>${jacoco-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>jacoco-check</id>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
<configuration>
|
||||
<dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
|
||||
<rules>
|
||||
<rule>
|
||||
<element>BUNDLE</element>
|
||||
<limits>
|
||||
<limit>
|
||||
<counter>INSTRUCTION</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>1</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,20 @@
|
|||
package ch.phoenix.oss.quarkus.commons.audit;
|
||||
|
||||
import jakarta.enterprise.inject.spi.CDI;
|
||||
import org.hibernate.envers.RevisionListener;
|
||||
|
||||
public class AuditRevisionListener implements RevisionListener {
|
||||
|
||||
@Override
|
||||
public void newRevision(Object revisionEntity) {
|
||||
var provider = CDI.current().select(RevisionContextProvider.class).get();
|
||||
|
||||
var rev = (Revision) revisionEntity;
|
||||
rev.actor = provider.getActor();
|
||||
rev.traceId = provider.getTraceId();
|
||||
rev.spanId = provider.getSpanId();
|
||||
rev.requestId = provider.getRequestId();
|
||||
rev.clientIp = provider.getClientIp();
|
||||
rev.hostName = provider.getHostName();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package ch.phoenix.oss.quarkus.commons.audit;
|
||||
|
||||
import io.quarkus.hibernate.orm.panache.PanacheEntity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import java.time.Instant;
|
||||
import org.hibernate.envers.NotAudited;
|
||||
|
||||
/**
|
||||
* The goal of this class is to have on the entity itself the exact same
|
||||
* timestamps as the ones from revisions generated by envers. Because of that,
|
||||
* we can't use @CreationTimestamp and @UpdateTimestamp, as those timestamp values
|
||||
* are managed by different Hibernate classes, so the generated values will drift.
|
||||
* <p>
|
||||
* Manually setting these values to match envers revisions would be error-prone,
|
||||
* verbose and tedious. So, the recommendation is to implement triggers on the
|
||||
* audit tables which will update the main entity whenever a revision is created.
|
||||
* An example of how to do that can be found in this module's integration tests.
|
||||
*/
|
||||
@MappedSuperclass
|
||||
public abstract class AuditedPanacheEntity extends PanacheEntity {
|
||||
|
||||
@NotAudited
|
||||
@Column(updatable = false)
|
||||
public Instant createdAt;
|
||||
|
||||
@NotAudited
|
||||
@Column(updatable = false)
|
||||
public Instant lastUpdatedAt;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package ch.phoenix.oss.quarkus.commons.audit;
|
||||
|
||||
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import java.time.Instant;
|
||||
import org.hibernate.envers.NotAudited;
|
||||
|
||||
/**
|
||||
* The goal of this class is to have on the entity itself the exact same
|
||||
* timestamps as the ones from revisions generated by envers. Because of that,
|
||||
* we can't use @CreationTimestamp and @UpdateTimestamp, as those timestamp values
|
||||
* are managed by different Hibernate classes, so the generated values will drift.
|
||||
* <p>
|
||||
* Manually setting these values to match envers revisions would be error-prone,
|
||||
* verbose and tedious. So, the recommendation is to implement triggers on the
|
||||
* audit tables which will update the main entity whenever a revision is created.
|
||||
* An example of how to do that can be found in this module's integration tests.
|
||||
*/
|
||||
@MappedSuperclass
|
||||
public abstract class AuditedPanacheEntityBase extends PanacheEntityBase {
|
||||
|
||||
@NotAudited
|
||||
@Column(updatable = false)
|
||||
public Instant createdAt;
|
||||
|
||||
@NotAudited
|
||||
@Column(updatable = false)
|
||||
public Instant lastUpdatedAt;
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package ch.phoenix.oss.quarkus.commons.audit;
|
||||
|
||||
import ch.phoenix.oss.quarkus.commons.tracing.TracingService;
|
||||
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||
import io.quarkus.arc.DefaultBean;
|
||||
import io.quarkus.arc.Unremovable;
|
||||
import io.quarkus.logging.Log;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
@Unremovable
|
||||
@DefaultBean
|
||||
@ApplicationScoped
|
||||
class DefaultRevisionContextProvider implements RevisionContextProvider {
|
||||
|
||||
private static final String UNKNOWN = "unknown";
|
||||
|
||||
private final TracingService tracingService;
|
||||
|
||||
@Inject
|
||||
DefaultRevisionContextProvider(TracingService tracingService) {
|
||||
this.tracingService = tracingService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@WithSpan
|
||||
public String getActor() {
|
||||
return tracingService.getActor();
|
||||
}
|
||||
|
||||
@Override
|
||||
@WithSpan
|
||||
public String getTraceId() {
|
||||
return tracingService.getTraceId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@WithSpan
|
||||
public String getSpanId() {
|
||||
return tracingService.getSpanId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@WithSpan
|
||||
public String getRequestId() {
|
||||
return tracingService.getRequestId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@WithSpan
|
||||
public String getClientIp() {
|
||||
return tracingService.getClientIp();
|
||||
}
|
||||
|
||||
@Override
|
||||
@WithSpan
|
||||
public String getHostName() {
|
||||
try {
|
||||
return InetAddress.getLocalHost().getHostName();
|
||||
} catch (UnknownHostException e) {
|
||||
Log.error("Unable to determine host name", e);
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package ch.phoenix.oss.quarkus.commons.audit;
|
||||
|
||||
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
|
||||
import jakarta.persistence.*;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import org.hibernate.envers.RevisionEntity;
|
||||
import org.hibernate.envers.RevisionNumber;
|
||||
import org.hibernate.envers.RevisionTimestamp;
|
||||
|
||||
@Entity
|
||||
@Table(name = "revinfo")
|
||||
@RevisionEntity(AuditRevisionListener.class)
|
||||
public class Revision extends PanacheEntityBase {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@RevisionNumber
|
||||
public long rev;
|
||||
|
||||
@RevisionTimestamp
|
||||
@Column(nullable = false, updatable = false)
|
||||
public Instant timestamp;
|
||||
|
||||
@Column(updatable = false)
|
||||
public String actor;
|
||||
|
||||
@Column(updatable = false)
|
||||
public String traceId;
|
||||
|
||||
@Column(updatable = false)
|
||||
public String spanId;
|
||||
|
||||
@Column(updatable = false)
|
||||
public String requestId;
|
||||
|
||||
@Column(updatable = false)
|
||||
public String clientIp;
|
||||
|
||||
@Column(updatable = false)
|
||||
public String hostName;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Revision that)) return false;
|
||||
return rev == that.rev;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(rev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Revision{rev=" + rev + '}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package ch.phoenix.oss.quarkus.commons.audit;
|
||||
|
||||
public interface RevisionContextProvider {
|
||||
|
||||
String getActor();
|
||||
|
||||
String getTraceId();
|
||||
|
||||
String getSpanId();
|
||||
|
||||
String getRequestId();
|
||||
|
||||
String getClientIp();
|
||||
|
||||
String getHostName();
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package ch.phoenix.oss.quarkus.commons.audit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import ch.phoenix.oss.quarkus.commons.tracing.TracingService;
|
||||
import io.quarkus.narayana.jta.QuarkusTransaction;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.mockito.InjectSpy;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.time.Instant;
|
||||
import org.hibernate.envers.AuditReaderFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class AuditedPanacheEntityBaseTest {
|
||||
|
||||
@Inject
|
||||
EntityManager entityManager;
|
||||
|
||||
@InjectSpy
|
||||
TracingService tracingService;
|
||||
|
||||
@SuppressWarnings("Convert2MethodRef")
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
QuarkusTransaction.requiringNew().run(() -> TestEntity2.deleteAll());
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistAndUpdate() {
|
||||
when(tracingService.getRequestId()).thenReturn("00000000-0000-0000-0000-000000000000");
|
||||
when(tracingService.getSpanId()).thenReturn("0000000000000000");
|
||||
when(tracingService.getTraceId()).thenReturn("00000000000000000000000000000000");
|
||||
when(tracingService.getActor()).thenReturn("unknown");
|
||||
when(tracingService.getClientIp()).thenReturn("unknown");
|
||||
|
||||
var now = Instant.now();
|
||||
|
||||
QuarkusTransaction.requiringNew().run(() -> {
|
||||
var entity = new TestEntity2("something");
|
||||
entity.persist();
|
||||
});
|
||||
|
||||
QuarkusTransaction.requiringNew().run(() -> {
|
||||
var entity = TestEntity2.findBySomething("something");
|
||||
assertAll(
|
||||
() -> assertThat(entity.createdAt)
|
||||
.as("createdAt should be after or equal to expected value")
|
||||
.isAfterOrEqualTo(now),
|
||||
() -> assertThat(entity.lastUpdatedAt)
|
||||
.as("lastUpdatedAt should be equal to createdAt")
|
||||
.isEqualTo(entity.createdAt));
|
||||
|
||||
var auditReader = AuditReaderFactory.get(entityManager);
|
||||
|
||||
var revisions = auditReader.getRevisions(TestEntity2.class, entity.id);
|
||||
assertThat(revisions).hasSize(1);
|
||||
|
||||
var revInfo = entityManager.find(Revision.class, revisions.getFirst());
|
||||
assertThat(revInfo).isNotNull();
|
||||
|
||||
assertAll(
|
||||
() -> assertThat(revInfo.timestamp)
|
||||
.as("revision timestamp should be equal to entity's createdAt timestamp")
|
||||
.isEqualTo(entity.createdAt),
|
||||
() -> assertThat(revInfo.actor)
|
||||
.as("actor should match expected value")
|
||||
.isEqualTo("unknown"),
|
||||
() -> assertThat(revInfo.traceId)
|
||||
.as("traceId should match expected value")
|
||||
.isEqualTo("00000000000000000000000000000000"),
|
||||
() -> assertThat(revInfo.spanId)
|
||||
.as("spanId should match expected value")
|
||||
.isEqualTo("0000000000000000"),
|
||||
() -> assertThat(revInfo.requestId)
|
||||
.as("requestId should match expected value")
|
||||
.isEqualTo("00000000-0000-0000-0000-000000000000"),
|
||||
() -> assertThat(revInfo.clientIp)
|
||||
.as("clientIp should match expected value")
|
||||
.isEqualTo("unknown"),
|
||||
() -> assertThat(revInfo.hostName)
|
||||
.as("hostName should not be blank")
|
||||
.isNotBlank());
|
||||
});
|
||||
|
||||
QuarkusTransaction.requiringNew().run(() -> {
|
||||
var entity = TestEntity2.findBySomething("something");
|
||||
entity.something = "else";
|
||||
});
|
||||
|
||||
QuarkusTransaction.requiringNew().run(() -> {
|
||||
var entity = TestEntity2.findBySomething("else");
|
||||
assertAll(() -> assertThat(entity.createdAt)
|
||||
.as("createdAt should be before lastUpdatedAt")
|
||||
.isBefore(entity.lastUpdatedAt));
|
||||
|
||||
var auditReader = AuditReaderFactory.get(entityManager);
|
||||
|
||||
var revisions = auditReader.getRevisions(TestEntity2.class, entity.id);
|
||||
assertThat(revisions).hasSize(2);
|
||||
|
||||
Revision revInfo = Revision.findById(revisions.getLast());
|
||||
assertThat(revInfo).isNotNull();
|
||||
|
||||
assertAll(
|
||||
() -> assertThat(revInfo.timestamp)
|
||||
.as("revision timestamp should not be equal to entity's createdAt")
|
||||
.isNotEqualTo(entity.createdAt),
|
||||
() -> assertThat(revInfo.timestamp)
|
||||
.as("revision timestamp should be equal to entity's lastUpdatedAt")
|
||||
.isEqualTo(entity.lastUpdatedAt),
|
||||
() -> assertThat(revInfo.actor)
|
||||
.as("actor should match expected value")
|
||||
.isEqualTo("unknown"),
|
||||
() -> assertThat(revInfo.traceId)
|
||||
.as("traceId should match expected value")
|
||||
.isEqualTo("00000000000000000000000000000000"),
|
||||
() -> assertThat(revInfo.spanId)
|
||||
.as("spanId should match expected value")
|
||||
.isEqualTo("0000000000000000"),
|
||||
() -> assertThat(revInfo.requestId)
|
||||
.as("requestId should match expected value")
|
||||
.isEqualTo("00000000-0000-0000-0000-000000000000"),
|
||||
() -> assertThat(revInfo.clientIp)
|
||||
.as("clientIp should match expected value")
|
||||
.isEqualTo("unknown"),
|
||||
() -> assertThat(revInfo.hostName)
|
||||
.as("hostName should not be blank")
|
||||
.isNotBlank());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package ch.phoenix.oss.quarkus.commons.audit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import ch.phoenix.oss.quarkus.commons.tracing.TracingService;
|
||||
import io.quarkus.narayana.jta.QuarkusTransaction;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.mockito.InjectSpy;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import java.time.Instant;
|
||||
import org.hibernate.envers.AuditReaderFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class AuditedPanacheEntityTest {
|
||||
|
||||
@Inject
|
||||
EntityManager entityManager;
|
||||
|
||||
@InjectSpy
|
||||
TracingService tracingService;
|
||||
|
||||
@SuppressWarnings("Convert2MethodRef")
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
QuarkusTransaction.requiringNew().run(() -> TestEntity.deleteAll());
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistAndUpdate() {
|
||||
when(tracingService.getRequestId()).thenReturn("00000000-0000-0000-0000-000000000000");
|
||||
when(tracingService.getSpanId()).thenReturn("0000000000000000");
|
||||
when(tracingService.getTraceId()).thenReturn("00000000000000000000000000000000");
|
||||
when(tracingService.getActor()).thenReturn("unknown");
|
||||
when(tracingService.getClientIp()).thenReturn("unknown");
|
||||
|
||||
var now = Instant.now();
|
||||
|
||||
QuarkusTransaction.requiringNew().run(() -> {
|
||||
var entity = new TestEntity("something");
|
||||
entity.persist();
|
||||
});
|
||||
|
||||
QuarkusTransaction.requiringNew().run(() -> {
|
||||
var entity = TestEntity.findBySomething("something");
|
||||
assertAll(
|
||||
() -> assertThat(entity.createdAt)
|
||||
.as("createdAt should be after or equal to expected value")
|
||||
.isAfterOrEqualTo(now),
|
||||
() -> assertThat(entity.lastUpdatedAt)
|
||||
.as("lastUpdatedAt should be equal to createdAt")
|
||||
.isEqualTo(entity.createdAt));
|
||||
|
||||
var auditReader = AuditReaderFactory.get(entityManager);
|
||||
|
||||
var revisions = auditReader.getRevisions(TestEntity.class, entity.id);
|
||||
assertThat(revisions).hasSize(1);
|
||||
|
||||
var revInfo = entityManager.find(Revision.class, revisions.getFirst());
|
||||
assertThat(revInfo).isNotNull();
|
||||
|
||||
assertAll(
|
||||
() -> assertThat(revInfo.timestamp)
|
||||
.as("revision timestamp should be equal to entity's createdAt timestamp")
|
||||
.isEqualTo(entity.createdAt),
|
||||
() -> assertThat(revInfo.actor)
|
||||
.as("actor should match expected value")
|
||||
.isEqualTo("unknown"),
|
||||
() -> assertThat(revInfo.traceId)
|
||||
.as("traceId should match expected value")
|
||||
.isEqualTo("00000000000000000000000000000000"),
|
||||
() -> assertThat(revInfo.spanId)
|
||||
.as("spanId should match expected value")
|
||||
.isEqualTo("0000000000000000"),
|
||||
() -> assertThat(revInfo.requestId)
|
||||
.as("requestId should match expected value")
|
||||
.isEqualTo("00000000-0000-0000-0000-000000000000"),
|
||||
() -> assertThat(revInfo.clientIp)
|
||||
.as("clientIp should match expected value")
|
||||
.isEqualTo("unknown"),
|
||||
() -> assertThat(revInfo.hostName)
|
||||
.as("hostName should not be blank")
|
||||
.isNotBlank());
|
||||
});
|
||||
|
||||
QuarkusTransaction.requiringNew().run(() -> {
|
||||
var entity = TestEntity.findBySomething("something");
|
||||
entity.something = "else";
|
||||
});
|
||||
|
||||
QuarkusTransaction.requiringNew().run(() -> {
|
||||
var entity = TestEntity.findBySomething("else");
|
||||
assertAll(() -> assertThat(entity.createdAt)
|
||||
.as("createdAt should be before lastUpdatedAt")
|
||||
.isBefore(entity.lastUpdatedAt));
|
||||
|
||||
var auditReader = AuditReaderFactory.get(entityManager);
|
||||
|
||||
var revisions = auditReader.getRevisions(TestEntity.class, entity.id);
|
||||
assertThat(revisions).hasSize(2);
|
||||
|
||||
Revision revInfo = Revision.findById(revisions.getLast());
|
||||
assertThat(revInfo).isNotNull();
|
||||
|
||||
assertAll(
|
||||
() -> assertThat(revInfo.timestamp)
|
||||
.as("revision timestamp should not be equal to entity's createdAt")
|
||||
.isNotEqualTo(entity.createdAt),
|
||||
() -> assertThat(revInfo.timestamp)
|
||||
.as("revision timestamp should be equal to entity's lastUpdatedAt")
|
||||
.isEqualTo(entity.lastUpdatedAt),
|
||||
() -> assertThat(revInfo.actor)
|
||||
.as("actor should match expected value")
|
||||
.isEqualTo("unknown"),
|
||||
() -> assertThat(revInfo.traceId)
|
||||
.as("traceId should match expected value")
|
||||
.isEqualTo("00000000000000000000000000000000"),
|
||||
() -> assertThat(revInfo.spanId)
|
||||
.as("spanId should match expected value")
|
||||
.isEqualTo("0000000000000000"),
|
||||
() -> assertThat(revInfo.requestId)
|
||||
.as("requestId should match expected value")
|
||||
.isEqualTo("00000000-0000-0000-0000-000000000000"),
|
||||
() -> assertThat(revInfo.clientIp)
|
||||
.as("clientIp should match expected value")
|
||||
.isEqualTo("unknown"),
|
||||
() -> assertThat(revInfo.hostName)
|
||||
.as("hostName should not be blank")
|
||||
.isNotBlank());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package ch.phoenix.oss.quarkus.commons.audit;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class DefaultRevisionContextProviderTest {
|
||||
|
||||
@Inject
|
||||
DefaultRevisionContextProvider underTest;
|
||||
|
||||
@Test
|
||||
void getHostName() {
|
||||
assertThat(underTest.getHostName()).isNotBlank();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getHostNameWhenUnknown() {
|
||||
try (var inetMock = mockStatic(InetAddress.class)) {
|
||||
inetMock.when(InetAddress::getLocalHost).thenThrow(new UnknownHostException("simulated failure"));
|
||||
|
||||
assertThat(underTest.getHostName()).isEqualTo("unknown");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package ch.phoenix.oss.quarkus.commons.audit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class RevisionTest {
|
||||
|
||||
@Test
|
||||
void testEquals() {
|
||||
var r1 = new Revision();
|
||||
r1.rev = 1;
|
||||
|
||||
var r2 = new Revision();
|
||||
r2.rev = 1;
|
||||
|
||||
var r3 = new Revision();
|
||||
r3.rev = 2;
|
||||
|
||||
assertThat(r1)
|
||||
.as("Revisions equality should should match expected value")
|
||||
.isEqualTo(r1)
|
||||
.isEqualTo(r2)
|
||||
.isNotEqualTo(r3)
|
||||
.isNotEqualTo(new Object());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHashCode() {
|
||||
var r1 = new Revision();
|
||||
r1.rev = 123;
|
||||
|
||||
var r2 = new Revision();
|
||||
r2.rev = 123;
|
||||
|
||||
var r3 = new Revision();
|
||||
r3.rev = 2;
|
||||
|
||||
assertThat(r1.hashCode()).isEqualTo(123).isEqualTo(r2.hashCode()).isNotEqualTo(r3.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToString() {
|
||||
var rev = new Revision();
|
||||
rev.rev = 1;
|
||||
|
||||
assertThat(rev).as("Revision's toString should match expected value").hasToString("Revision{rev=1}");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package ch.phoenix.oss.quarkus.commons.audit;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import org.hibernate.envers.Audited;
|
||||
|
||||
@Entity
|
||||
@Audited
|
||||
@Table(name = "test_entity")
|
||||
public class TestEntity extends AuditedPanacheEntity {
|
||||
|
||||
public String something;
|
||||
|
||||
public TestEntity() {}
|
||||
|
||||
public TestEntity(String something) {
|
||||
this.something = something;
|
||||
}
|
||||
|
||||
public static TestEntity findBySomething(String something) {
|
||||
return find("something", something).singleResult();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package ch.phoenix.oss.quarkus.commons.audit;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import org.hibernate.envers.Audited;
|
||||
|
||||
@Entity
|
||||
@Audited
|
||||
@Table(name = "test_entity")
|
||||
public class TestEntity2 extends AuditedPanacheEntityBase {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
public Long id;
|
||||
|
||||
public String something;
|
||||
|
||||
public TestEntity2() {}
|
||||
|
||||
public TestEntity2(String something) {
|
||||
this.something = something;
|
||||
}
|
||||
|
||||
public static TestEntity2 findBySomething(String something) {
|
||||
return find("something", something).singleResult();
|
||||
}
|
||||
}
|
22
quarkus-audit-tools/src/test/resources/application.yaml
Normal file
22
quarkus-audit-tools/src/test/resources/application.yaml
Normal file
|
@ -0,0 +1,22 @@
|
|||
quarkus:
|
||||
flyway:
|
||||
migrate-at-start: true
|
||||
datasource:
|
||||
db-kind: postgresql
|
||||
hibernate-orm:
|
||||
sql-load-script: no-file
|
||||
schema-management:
|
||||
strategy: none
|
||||
log:
|
||||
sql: true
|
||||
bind-parameters: true
|
||||
hibernate-envers:
|
||||
audit-strategy: org.hibernate.envers.strategy.internal.ValidityAuditStrategy
|
||||
revision-listener: ch.phoenix.oss.quarkus.commons.audit.AuditRevisionListener
|
||||
security:
|
||||
users:
|
||||
embedded:
|
||||
enabled: true
|
||||
plain-text: true
|
||||
users:
|
||||
jon: doe
|
|
@ -0,0 +1,46 @@
|
|||
create sequence revinfo_seq start with 1 increment by 50;
|
||||
create sequence test_entity_seq start with 1 increment by 50;
|
||||
|
||||
create table revinfo
|
||||
(
|
||||
rev bigint not null,
|
||||
timestamp timestamp(6) with time zone not null,
|
||||
actor varchar(255),
|
||||
spanId varchar(255),
|
||||
traceId varchar(255),
|
||||
requestId varchar(255),
|
||||
clientIp varchar(255),
|
||||
hostName varchar(255),
|
||||
primary key (rev)
|
||||
);
|
||||
|
||||
create table test_entity
|
||||
(
|
||||
|
||||
id bigint primary key not null,
|
||||
something varchar(255),
|
||||
createdAt timestamp(6) with time zone,
|
||||
lastUpdatedAt timestamp(6) with time zone
|
||||
);
|
||||
|
||||
create table test_entity_aud
|
||||
(
|
||||
revtype smallint,
|
||||
rev bigint not null,
|
||||
revend bigint,
|
||||
id bigint not null,
|
||||
something varchar(255),
|
||||
primary key (rev, id)
|
||||
);
|
||||
|
||||
alter table if exists test_entity_aud
|
||||
add constraint fk_rev__revinfo_rev
|
||||
foreign key (rev)
|
||||
references revinfo;
|
||||
|
||||
|
||||
alter table if exists test_entity_aud
|
||||
add constraint fk_revend__revinfo_rev
|
||||
foreign key (revend)
|
||||
references revinfo;
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
CREATE OR REPLACE FUNCTION trg_test_entity_aud_apply_rev()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $func$
|
||||
DECLARE
|
||||
ts TIMESTAMP;
|
||||
BEGIN
|
||||
-- fetch the exact revision timestamp from revinfo
|
||||
SELECT r.timestamp
|
||||
INTO ts
|
||||
FROM revinfo r
|
||||
WHERE r.rev = NEW.rev;
|
||||
|
||||
-- only set created_at once, when still NULL
|
||||
UPDATE test_entity
|
||||
SET createdAt = ts
|
||||
WHERE id = NEW.id
|
||||
AND createdAt IS NULL;
|
||||
|
||||
-- always bump last_updated_at
|
||||
UPDATE test_entity
|
||||
SET lastUpdatedAt = ts
|
||||
WHERE id = NEW.id;
|
||||
|
||||
RETURN NULL;
|
||||
END;
|
||||
$func$;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_test_entity_aud_after_insert
|
||||
ON test_entity_aud;
|
||||
|
||||
CREATE TRIGGER trg_test_entity_aud_after_insert
|
||||
AFTER INSERT ON test_entity_aud
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION trg_test_entity_aud_apply_rev();
|
37
quarkus-audit-tools/src/test/resources/import.sql
Normal file
37
quarkus-audit-tools/src/test/resources/import.sql
Normal file
|
@ -0,0 +1,37 @@
|
|||
-- 1) Create or replace the trigger function
|
||||
CREATE OR REPLACE FUNCTION trg_test_entity_aud_apply_rev()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $func$
|
||||
DECLARE
|
||||
ts TIMESTAMP;
|
||||
BEGIN
|
||||
-- fetch the exact revision timestamp from revinfo
|
||||
SELECT r.timestamp
|
||||
INTO ts
|
||||
FROM revinfo r
|
||||
WHERE r.rev = NEW.rev;
|
||||
|
||||
-- only set created_at once, when still NULL
|
||||
UPDATE test_entity
|
||||
SET created_at = ts
|
||||
WHERE id = NEW.id
|
||||
AND created_at IS NULL;
|
||||
|
||||
-- always bump last_updated_at
|
||||
UPDATE test_entity
|
||||
SET last_updated_at = ts
|
||||
WHERE id = NEW.id;
|
||||
|
||||
RETURN NULL; -- AFTER trigger ignores return value
|
||||
END;
|
||||
$func$;
|
||||
|
||||
-- 2) Drop any existing trigger, then attach the new one
|
||||
DROP TRIGGER IF EXISTS trg_test_entity_aud_after_insert
|
||||
ON test_entity_aud;
|
||||
|
||||
CREATE TRIGGER trg_test_entity_aud_after_insert
|
||||
AFTER INSERT ON test_entity_aud
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION trg_test_entity_aud_apply_rev();
|
65
quarkus-client-logger/pom.xml
Normal file
65
quarkus-client-logger/pom.xml
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?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>ch.phoenix.oss</groupId>
|
||||
<artifactId>quarkus-commons</artifactId>
|
||||
<version>1.0.9-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>quarkus-client-logger</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest-client</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-config-yaml</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest-jackson</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>${jacoco-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>jacoco-check</id>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
<configuration>
|
||||
<dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
|
||||
<rules>
|
||||
<rule>
|
||||
<element>BUNDLE</element>
|
||||
<limits>
|
||||
<limit>
|
||||
<counter>INSTRUCTION</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.92</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,10 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.Converter;
|
||||
|
||||
public class LowerCaseStringConverter implements Converter<String> {
|
||||
@Override
|
||||
public String convert(String value) throws IllegalArgumentException, NullPointerException {
|
||||
return value.toLowerCase();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.buffer.Buffer;
|
||||
import io.vertx.core.http.HttpClientRequest;
|
||||
import io.vertx.core.http.HttpClientResponse;
|
||||
import jakarta.enterprise.context.Dependent;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.reactive.client.api.ClientLogger;
|
||||
|
||||
/**
|
||||
* This is based on org.jboss.resteasy.reactive.client.logging.DefaultClientLogger,
|
||||
* with the only change being that headers are redacted based on the Set provided
|
||||
* by the configuration.
|
||||
*/
|
||||
@Dependent
|
||||
public class RedactingClientLogger implements ClientLogger {
|
||||
|
||||
private static final Logger log = Logger.getLogger(RedactingClientLogger.class);
|
||||
|
||||
private static final String REDACTED_VALUE = "*****";
|
||||
|
||||
private final Set<String> redactedHeaders;
|
||||
|
||||
private int bodySize;
|
||||
|
||||
@Inject
|
||||
public RedactingClientLogger(RedactingClientLoggerConfiguration configuration) {
|
||||
this.redactedHeaders = configuration
|
||||
.headers()
|
||||
.redact()
|
||||
.orElse(RedactingClientLoggerConfiguration.Headers.DEFAULT_REDACTED_HEADERS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBodySize(int bodySize) {
|
||||
this.bodySize = bodySize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logResponse(HttpClientResponse response, boolean redirect) {
|
||||
if (!log.isDebugEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection Convert2Lambda
|
||||
response.bodyHandler(new Handler<>() {
|
||||
@Override
|
||||
public void handle(Buffer body) {
|
||||
log.debugf(
|
||||
"%s: %s %s, Status[%d %s], Headers[%s], Body:\n%s",
|
||||
redirect ? "Redirect" : "Response",
|
||||
response.request().getMethod(),
|
||||
response.request().absoluteURI(),
|
||||
response.statusCode(),
|
||||
response.statusMessage(),
|
||||
asString(response.headers()),
|
||||
bodyToString(body));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logRequest(HttpClientRequest request, Buffer body, boolean omitBody) {
|
||||
if (!log.isDebugEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (omitBody) {
|
||||
log.debugf(
|
||||
"Request: %s %s Headers[%s], Body omitted",
|
||||
request.getMethod(), request.absoluteURI(), asString(request.headers()));
|
||||
} else if (body == null || body.length() == 0) {
|
||||
log.debugf(
|
||||
"Request: %s %s Headers[%s], Empty body",
|
||||
request.getMethod(), request.absoluteURI(), asString(request.headers()));
|
||||
} else {
|
||||
log.debugf(
|
||||
"Request: %s %s Headers[%s], Body:\n%s",
|
||||
request.getMethod(), request.absoluteURI(), asString(request.headers()), bodyToString(body));
|
||||
}
|
||||
}
|
||||
|
||||
private String bodyToString(Buffer body) {
|
||||
if (body == null) {
|
||||
return "";
|
||||
} else if (bodySize <= 0) {
|
||||
return body.toString();
|
||||
} else {
|
||||
String bodyAsString = body.toString();
|
||||
return bodyAsString.substring(0, Math.min(bodySize, bodyAsString.length()));
|
||||
}
|
||||
}
|
||||
|
||||
private String asString(MultiMap headers) {
|
||||
if (headers.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder((headers.size() * (6 + 1 + 6))
|
||||
+ (headers.size() - 1)); // this is a very rough estimate of a result like 'key1=value1 key2=value2'
|
||||
boolean isFirst = true;
|
||||
for (Map.Entry<String, String> entry : headers) {
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else {
|
||||
sb.append(' ');
|
||||
}
|
||||
|
||||
var key = entry.getKey();
|
||||
var value = redactedHeaders.contains(key.toLowerCase()) ? REDACTED_VALUE : entry.getValue();
|
||||
|
||||
sb.append(key).append('=').append(value);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import io.smallrye.config.ConfigMapping;
|
||||
import io.smallrye.config.WithConverter;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@ConfigMapping(prefix = "phoenix.client-logger")
|
||||
public interface RedactingClientLoggerConfiguration {
|
||||
|
||||
Headers headers();
|
||||
|
||||
interface Headers {
|
||||
|
||||
Set<String> DEFAULT_REDACTED_HEADERS = Set.of(HttpHeaders.AUTHORIZATION.toLowerCase());
|
||||
|
||||
Optional<Set<@WithConverter(LowerCaseStringConverter.class) String>> redact();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTestProfile;
|
||||
import java.util.Map;
|
||||
|
||||
public class InfoLevelProfile implements QuarkusTestProfile {
|
||||
|
||||
@Override
|
||||
public Map<String, String> getConfigOverrides() {
|
||||
return Map.of("quarkus.log.category.\"ch.phoenix.oss.quarkus.commons.client.logger\".level", "INFO");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.TestProfile;
|
||||
import jakarta.inject.Inject;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
@TestProfile(InfoLevelProfile.class)
|
||||
class InfoLevelTest {
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
TestClient injectedClient;
|
||||
|
||||
TestClient builtClient = QuarkusRestClientBuilder.newBuilder()
|
||||
.clientLogger(new RedactingClientLogger(() -> Optional::empty))
|
||||
.baseUri(URI.create("http://localhost:8087"))
|
||||
.build(TestClient.class);
|
||||
|
||||
@Test
|
||||
void getWithInjectedClient() {
|
||||
injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithBuiltClientAndEmptyConfig() {
|
||||
builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithInjectedClient() {
|
||||
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body");
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithBuiltClientAndEmptyConfig() {
|
||||
builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class RedactingClientLoggerTest {
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
TestClient injectedClient;
|
||||
|
||||
TestClient builtClient = QuarkusRestClientBuilder.newBuilder()
|
||||
.clientLogger(new RedactingClientLogger(() -> Optional::empty))
|
||||
.baseUri(URI.create("http://localhost:8087"))
|
||||
.build(TestClient.class);
|
||||
|
||||
@Test
|
||||
void getWithInjectedClient() {
|
||||
injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithBuiltClientAndEmptyConfig() {
|
||||
builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithInjectedClient() {
|
||||
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body");
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithInjectedClientAndNullBody() {
|
||||
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithBuiltClientAndEmptyConfig() {
|
||||
builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTestProfile;
|
||||
import java.util.Map;
|
||||
|
||||
public class ScopeNoneProfile implements QuarkusTestProfile {
|
||||
|
||||
@Override
|
||||
public Map<String, String> getConfigOverrides() {
|
||||
return Map.of("quarkus.rest-client.logging.scope", "none");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.TestProfile;
|
||||
import jakarta.inject.Inject;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
@TestProfile(ScopeNoneProfile.class)
|
||||
class ScopeNoneTest {
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
TestClient injectedClient;
|
||||
|
||||
TestClient builtClient = QuarkusRestClientBuilder.newBuilder()
|
||||
.clientLogger(new RedactingClientLogger(() -> Optional::empty))
|
||||
.baseUri(URI.create("http://localhost:8087"))
|
||||
.build(TestClient.class);
|
||||
|
||||
@Test
|
||||
void getWithInjectedClient() {
|
||||
injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWithBuiltClientAndEmptyConfig() {
|
||||
builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted");
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithInjectedClient() {
|
||||
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body");
|
||||
}
|
||||
|
||||
@Test
|
||||
void postWithBuiltClientAndEmptyConfig() {
|
||||
builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
@SuppressWarnings("UastIncorrectHttpHeaderInspection")
|
||||
@RegisterRestClient(configKey = "test")
|
||||
public interface TestClient {
|
||||
|
||||
@GET
|
||||
@Path("/")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
String get(
|
||||
@HeaderParam("Authorization") String authorization,
|
||||
@HeaderParam("X-Request-ID") String requestId,
|
||||
@HeaderParam("X-Something-Else") String somethingElse);
|
||||
|
||||
@POST
|
||||
@Path("/")
|
||||
@Consumes(MediaType.TEXT_PLAIN)
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
String post(
|
||||
@HeaderParam("Authorization") String authorization,
|
||||
@HeaderParam("X-Request-ID") String requestId,
|
||||
@HeaderParam("X-Something-Else") String somethingElse,
|
||||
String body);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package ch.phoenix.oss.quarkus.commons.client.logger;
|
||||
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
@Path("/")
|
||||
public class TestResource {
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public String get() {
|
||||
return "get";
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes(MediaType.TEXT_PLAIN)
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public String post(String body) {
|
||||
return body;
|
||||
}
|
||||
}
|
20
quarkus-client-logger/src/test/resources/application.yaml
Normal file
20
quarkus-client-logger/src/test/resources/application.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
quarkus:
|
||||
http:
|
||||
test-port: 8087
|
||||
rest-client:
|
||||
logging:
|
||||
scope: request-response
|
||||
body-limit: 10000
|
||||
test:
|
||||
url: http://localhost:${quarkus.http.test-port}
|
||||
log:
|
||||
category:
|
||||
"ch.phoenix.oss.quarkus.commons.client.logger":
|
||||
level: DEBUG
|
||||
|
||||
phoenix:
|
||||
client-logger:
|
||||
headers:
|
||||
redact:
|
||||
- AUTHORIZATION
|
||||
- X-SOMETHING-ELSE
|
|
@ -1,13 +1,11 @@
|
|||
<?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">
|
||||
<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>ch.phoenixtechnologies.quarkus</groupId>
|
||||
<groupId>ch.phoenix.oss</groupId>
|
||||
<artifactId>quarkus-commons</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<version>1.0.9-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>quarkus-clock-service</artifactId>
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.clock;
|
||||
package ch.phoenix.oss.quarkus.commons.clock;
|
||||
|
||||
import io.quarkus.arc.DefaultBean;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.enterprise.inject.Produces;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
class ClockProducer {
|
||||
|
@ -14,5 +13,4 @@ class ClockProducer {
|
|||
Clock produceClock() {
|
||||
return Clock.systemDefaultZone();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package ch.phoenix.oss.quarkus.commons.clock;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
public interface ClockService {
|
||||
|
||||
Instant instant();
|
||||
|
||||
long currentTimeMillis();
|
||||
|
||||
LocalDate localDate();
|
||||
|
||||
LocalDateTime localDateTime();
|
||||
|
||||
ZonedDateTime zonedDateTime();
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.clock;
|
||||
package ch.phoenix.oss.quarkus.commons.clock;
|
||||
|
||||
import io.quarkus.arc.DefaultBean;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.*;
|
||||
|
||||
@DefaultBean
|
||||
@ApplicationScoped
|
||||
|
@ -25,4 +23,19 @@ class ClockServiceImpl implements ClockService {
|
|||
public long currentTimeMillis() {
|
||||
return clock.millis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate localDate() {
|
||||
return LocalDate.now(clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDateTime localDateTime() {
|
||||
return LocalDateTime.now(clock);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZonedDateTime zonedDateTime() {
|
||||
return ZonedDateTime.now(clock);
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.clock;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public interface ClockService {
|
||||
|
||||
Instant instant();
|
||||
|
||||
long currentTimeMillis();
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.clock;
|
||||
package ch.phoenix.oss.quarkus.commons.clock;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class ClockProducerTest {
|
||||
|
@ -21,4 +19,4 @@ class ClockProducerTest {
|
|||
.as("Produced clock's zone should match expected value")
|
||||
.isEqualTo(Clock.systemDefaultZone().getZone());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package ch.phoenix.oss.quarkus.commons.clock;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.quarkus.test.InjectMock;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import java.time.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class ClockServiceImplTest {
|
||||
|
||||
@Inject
|
||||
ClockService clockService;
|
||||
|
||||
@InjectMock
|
||||
Clock clock;
|
||||
|
||||
@Test
|
||||
void instant() {
|
||||
var expected = Instant.ofEpochMilli(1729280640915L);
|
||||
when(clock.instant()).thenReturn(expected);
|
||||
|
||||
assertThat(clockService.instant())
|
||||
.as("Instant should match expected value")
|
||||
.isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentTimeMillis() {
|
||||
var expected = 1729280640915L;
|
||||
when(clock.millis()).thenReturn(expected);
|
||||
|
||||
assertThat(clockService.currentTimeMillis())
|
||||
.as("Instant should match expected value")
|
||||
.isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void localDate() {
|
||||
var instant = Instant.ofEpochMilli(1729280640915L);
|
||||
when(clock.instant()).thenReturn(instant);
|
||||
when(clock.getZone()).thenReturn(ZoneId.of("UTC"));
|
||||
assertThat(clockService.localDate())
|
||||
.as("LocalDate should match expected value")
|
||||
.isEqualTo(LocalDate.parse("2024-10-18"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void localDateTime() {
|
||||
var instant = Instant.ofEpochMilli(1729280640915L);
|
||||
when(clock.instant()).thenReturn(instant);
|
||||
when(clock.getZone()).thenReturn(ZoneId.of("UTC"));
|
||||
assertThat(clockService.localDateTime())
|
||||
.as("LocalDateTime should match expected value")
|
||||
.isEqualTo(LocalDateTime.parse("2024-10-18T19:44:00.915"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void zonedDateTime() {
|
||||
var instant = Instant.ofEpochMilli(1729280640915L);
|
||||
when(clock.instant()).thenReturn(instant);
|
||||
when(clock.getZone()).thenReturn(ZoneId.of("UTC"));
|
||||
assertThat(clockService.zonedDateTime())
|
||||
.as("ZonedDateTime should match expected value")
|
||||
.isEqualTo(ZonedDateTime.parse("2024-10-18T19:44:00.915Z[UTC]"));
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.clock;
|
||||
|
||||
import io.quarkus.test.InjectMock;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.mockito.InjectSpy;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@QuarkusTest
|
||||
class ClockServiceImplTest {
|
||||
|
||||
@Inject
|
||||
ClockService clockService;
|
||||
|
||||
@InjectMock
|
||||
Clock clock;
|
||||
|
||||
@Test
|
||||
void instant() {
|
||||
var expected = Instant.ofEpochMilli(1729280640915L);
|
||||
when(clock.instant()).thenReturn(expected);
|
||||
|
||||
assertThat(clockService.instant())
|
||||
.as("Instant should match expected value")
|
||||
.isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentTimeMillis() {
|
||||
var expected = 1729280640915L;
|
||||
when(clock.millis()).thenReturn(expected);
|
||||
|
||||
assertThat(clockService.currentTimeMillis())
|
||||
.as("Instant should match expected value")
|
||||
.isEqualTo(expected);
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
<?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">
|
||||
<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>ch.phoenixtechnologies.quarkus</groupId>
|
||||
<groupId>ch.phoenix.oss</groupId>
|
||||
<artifactId>quarkus-commons</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<version>1.0.9-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>quarkus-json-service</artifactId>
|
||||
|
@ -42,7 +40,7 @@
|
|||
<limit>
|
||||
<counter>INSTRUCTION</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.83</minimum>
|
||||
<minimum>1</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.json;
|
||||
package ch.phoenix.oss.quarkus.commons.json;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
|
@ -9,5 +9,4 @@ public interface JsonService {
|
|||
<T> T fromJson(String json, Class<T> clazz);
|
||||
|
||||
<T> T fromJson(String json, TypeReference<T> typeReference);
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.json;
|
||||
package ch.phoenix.oss.quarkus.commons.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
@ -30,10 +30,9 @@ class JsonServiceImpl implements JsonService {
|
|||
try {
|
||||
return objectMapper.readValue(json, clazz);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException("Unable to read object of class [" + clazz.getName()
|
||||
+ "] from json String: " + json, e);
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to read object of class [" + clazz.getName() + "] from json String: " + json, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -41,8 +40,10 @@ class JsonServiceImpl implements JsonService {
|
|||
try {
|
||||
return objectMapper.readValue(json, typeReference);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException("Unable to read object of type [" + typeReference.getType().getTypeName()
|
||||
+ "] from json String: " + json, e);
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to read object of type [" + typeReference.getType().getTypeName() + "] from json String: "
|
||||
+ json,
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +1,29 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.json;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
package ch.phoenix.oss.quarkus.commons.json;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class JsonServiceImplTest {
|
||||
|
||||
public static final TypeReference<List<TestRecord>> TYPE_REFERENCE = new TypeReference<>() {
|
||||
};
|
||||
public static final TypeReference<List<TestRecord>> TYPE_REFERENCE = new TypeReference<>() {};
|
||||
|
||||
record TestRecord(String name, int age) {
|
||||
record TestRecord(String name, int age) {}
|
||||
|
||||
static class CircularReference {
|
||||
CircularReference reference;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CircularReference";
|
||||
}
|
||||
}
|
||||
|
||||
@Inject
|
||||
|
@ -29,9 +35,18 @@ class JsonServiceImplTest {
|
|||
|
||||
var actual = jsonService.toJson(new TestRecord("John Doe", 30));
|
||||
|
||||
assertThat(actual)
|
||||
.as("Json should match expected value")
|
||||
.isEqualTo(expected);
|
||||
assertThat(actual).as("Json should match expected value").isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toJsonWithCircularReference() {
|
||||
var circularReference = new CircularReference();
|
||||
circularReference.reference = circularReference;
|
||||
|
||||
assertThatThrownBy(() -> jsonService.toJson(circularReference))
|
||||
.as("Should throw IllegalArgumentException when input is invalid")
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Unable to write object as json String: CircularReference");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -41,9 +56,7 @@ class JsonServiceImplTest {
|
|||
|
||||
var actual = jsonService.fromJson(json, TestRecord.class);
|
||||
|
||||
assertThat(actual)
|
||||
.as("Deserialized object should match expected value")
|
||||
.isEqualTo(expected);
|
||||
assertThat(actual).as("Deserialized object should match expected value").isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -53,7 +66,8 @@ class JsonServiceImplTest {
|
|||
assertThatThrownBy(() -> jsonService.fromJson(json, TestRecord.class))
|
||||
.as("Should throw IllegalArgumentException when input is invalid")
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Unable to read object of class [ch.phoenixtechnologies.quarkus.commons.json.JsonServiceImplTest$TestRecord] from json String: {\"name\":\"John Doe\",\"age\":\"30\"");
|
||||
.hasMessage(
|
||||
"Unable to read object of class [ch.phoenix.oss.quarkus.commons.json.JsonServiceImplTest$TestRecord] from json String: {\"name\":\"John Doe\",\"age\":\"30\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -75,6 +89,7 @@ class JsonServiceImplTest {
|
|||
assertThatThrownBy(() -> jsonService.fromJson(json, TYPE_REFERENCE))
|
||||
.as("Should throw IllegalArgumentException when input is invalid")
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Unable to read object of type [java.util.List<ch.phoenixtechnologies.quarkus.commons.json.JsonServiceImplTest$TestRecord>] from json String: {\"name\":\"John Doe\",\"age\":30},{\"name\":\"Jane Doe\",\"age\":\"25\"}");
|
||||
.hasMessage(
|
||||
"Unable to read object of type [java.util.List<ch.phoenix.oss.quarkus.commons.json.JsonServiceImplTest$TestRecord>] from json String: {\"name\":\"John Doe\",\"age\":30},{\"name\":\"Jane Doe\",\"age\":\"25\"}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
<?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">
|
||||
<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>ch.phoenixtechnologies.quarkus</groupId>
|
||||
<groupId>ch.phoenix.oss</groupId>
|
||||
<artifactId>quarkus-commons</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<version>1.0.9-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>quarkus-message-digest-service</artifactId>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.digest;
|
||||
package ch.phoenix.oss.quarkus.commons.digest;
|
||||
|
||||
import io.smallrye.config.ConfigMapping;
|
||||
import io.smallrye.config.WithDefault;
|
||||
|
@ -8,5 +8,4 @@ public interface MessageDigestConfiguration {
|
|||
|
||||
@WithDefault("SHA-256")
|
||||
String defaultAlgorithm();
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.digest;
|
||||
package ch.phoenix.oss.quarkus.commons.digest;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.MessageDigest;
|
|
@ -1,8 +1,7 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.digest;
|
||||
package ch.phoenix.oss.quarkus.commons.digest;
|
||||
|
||||
import io.quarkus.arc.DefaultBean;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
|
@ -1,37 +1,37 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.digest;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
package ch.phoenix.oss.quarkus.commons.digest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
@QuarkusTest
|
||||
class MessageDigestServiceImplTest {
|
||||
|
||||
public static final String INPUT = "test";
|
||||
public static final byte[] EXPECTED_SHA256_DIGEST = {-97, -122, -48, -127, -120, 76, 125, 101, -102, 47, -22, -96, -59, 90, -48, 21, -93, -65, 79, 27, 43, 11, -126, 44, -47, 93, 108, 21, -80, -16, 10, 8};
|
||||
public static final byte[] EXPECTED_SHA256_DIGEST = {
|
||||
-97, -122, -48, -127, -120, 76, 125, 101, -102, 47, -22, -96, -59, 90, -48, 21, -93, -65, 79, 27, 43, 11, -126,
|
||||
44, -47, 93, 108, 21, -80, -16, 10, 8
|
||||
};
|
||||
|
||||
@Inject
|
||||
MessageDigestServiceImpl service;
|
||||
|
||||
@Test
|
||||
void getInstance() {
|
||||
assertThat(service.getInstance())
|
||||
.isInstanceOf(MessageDigest.class);
|
||||
assertThat(service.getInstance()).isInstanceOf(MessageDigest.class);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"SHA-256", "SHA-512", "MD5"})
|
||||
void getInstanceWithAlgorithm(String algorithm) {
|
||||
assertThat(service.getInstance(algorithm))
|
||||
.isInstanceOf(MessageDigest.class);
|
||||
assertThat(service.getInstance(algorithm)).isInstanceOf(MessageDigest.class);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -61,7 +61,6 @@ class MessageDigestServiceImplTest {
|
|||
assertThat(service.digest(INPUT, StandardCharsets.UTF_8))
|
||||
.as("Digest should match expected value")
|
||||
.isEqualTo(EXPECTED_SHA256_DIGEST);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -78,4 +77,4 @@ class MessageDigestServiceImplTest {
|
|||
.as("Digest should match expected value")
|
||||
.isEqualTo(EXPECTED_SHA256_DIGEST);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
<?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">
|
||||
<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>ch.phoenixtechnologies.quarkus</groupId>
|
||||
<groupId>ch.phoenix.oss</groupId>
|
||||
<artifactId>quarkus-commons</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<version>1.0.9-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>quarkus-random-number-generator</artifactId>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.random;
|
||||
package ch.phoenix.oss.quarkus.commons.random;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Random;
|
|
@ -1,8 +1,7 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.random;
|
||||
package ch.phoenix.oss.quarkus.commons.random;
|
||||
|
||||
import io.quarkus.arc.DefaultBean;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Random;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.random;
|
||||
package ch.phoenix.oss.quarkus.commons.random;
|
||||
|
||||
import java.util.Random;
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.random;
|
||||
package ch.phoenix.oss.quarkus.commons.random;
|
||||
|
||||
import io.quarkus.arc.DefaultBean;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
|
@ -19,6 +18,4 @@ class RandomProviderImpl implements RandomProvider {
|
|||
throw new IllegalStateException("Unable to obtain strong SecureRandom instance", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.random;
|
||||
package ch.phoenix.oss.quarkus.commons.random;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.mockito.InjectSpy;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class RandomNumberGeneratorImplTest {
|
||||
|
@ -26,9 +25,7 @@ class RandomNumberGeneratorImplTest {
|
|||
|
||||
var actual = generator.generateInt();
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated int should match expected value")
|
||||
.isEqualTo(-1305521323);
|
||||
assertThat(actual).as("Generated int should match expected value").isEqualTo(-1305521323);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -37,9 +34,7 @@ class RandomNumberGeneratorImplTest {
|
|||
|
||||
var actual = generator.generateInt(40);
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated int should match expected value")
|
||||
.isEqualTo(26);
|
||||
assertThat(actual).as("Generated int should match expected value").isEqualTo(26);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -48,21 +43,16 @@ class RandomNumberGeneratorImplTest {
|
|||
|
||||
var actual = generator.generateInt(1, 40);
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated int should match expected value")
|
||||
.isEqualTo(17);
|
||||
assertThat(actual).as("Generated int should match expected value").isEqualTo(17);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void generateLong() {
|
||||
when(provider.get()).thenReturn(getRandom());
|
||||
|
||||
var actual = generator.generateLong();
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated long should match expected value")
|
||||
.isEqualTo(-5607171386684657918L);
|
||||
assertThat(actual).as("Generated long should match expected value").isEqualTo(-5607171386684657918L);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -71,9 +61,7 @@ class RandomNumberGeneratorImplTest {
|
|||
|
||||
var actual = generator.generateLong(40L);
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated long should match expected value")
|
||||
.isEqualTo(9L);
|
||||
assertThat(actual).as("Generated long should match expected value").isEqualTo(9L);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -82,9 +70,7 @@ class RandomNumberGeneratorImplTest {
|
|||
|
||||
var actual = generator.generateLong(1L, 40L);
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated long should match expected value")
|
||||
.isEqualTo(23L);
|
||||
assertThat(actual).as("Generated long should match expected value").isEqualTo(23L);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -93,9 +79,7 @@ class RandomNumberGeneratorImplTest {
|
|||
|
||||
var actual = generator.generateFloat();
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated float should match expected value")
|
||||
.isEqualTo(0.6960346f);
|
||||
assertThat(actual).as("Generated float should match expected value").isEqualTo(0.6960346f);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -104,9 +88,7 @@ class RandomNumberGeneratorImplTest {
|
|||
|
||||
var actual = generator.generateFloat(0.99f);
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated float should match expected value")
|
||||
.isEqualTo(0.6890743f);
|
||||
assertThat(actual).as("Generated float should match expected value").isEqualTo(0.6890743f);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -115,9 +97,7 @@ class RandomNumberGeneratorImplTest {
|
|||
|
||||
var actual = generator.generateFloat(0.01f, 0.99f);
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated float should match expected value")
|
||||
.isEqualTo(0.69211394f);
|
||||
assertThat(actual).as("Generated float should match expected value").isEqualTo(0.69211394f);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -126,9 +106,7 @@ class RandomNumberGeneratorImplTest {
|
|||
|
||||
var actual = generator.generateDouble();
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated double should match expected value")
|
||||
.isEqualTo(0.6960346394874213d);
|
||||
assertThat(actual).as("Generated double should match expected value").isEqualTo(0.6960346394874213d);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -137,9 +115,7 @@ class RandomNumberGeneratorImplTest {
|
|||
|
||||
var actual = generator.generateDouble(0.99d);
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated double should match expected value")
|
||||
.isEqualTo(0.6890742930925471d);
|
||||
assertThat(actual).as("Generated double should match expected value").isEqualTo(0.6890742930925471d);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -148,9 +124,7 @@ class RandomNumberGeneratorImplTest {
|
|||
|
||||
var actual = generator.generateDouble(0.01d, 0.99d);
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated double should match expected value")
|
||||
.isEqualTo(0.6921139466976729d);
|
||||
assertThat(actual).as("Generated double should match expected value").isEqualTo(0.6921139466976729d);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -176,5 +150,4 @@ class RandomNumberGeneratorImplTest {
|
|||
private static Random getRandom() {
|
||||
return new Random(24353L);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,16 +1,15 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.random;
|
||||
package ch.phoenix.oss.quarkus.commons.random;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
@SuppressWarnings("removal")
|
||||
|
@ -47,16 +46,13 @@ class RandomProviderImplTest {
|
|||
|
||||
private static String getSecurityProperty() {
|
||||
return AccessController.doPrivileged(
|
||||
(PrivilegedAction<String>) () -> Security.getProperty(
|
||||
SECURE_RANDOM_STRONG_ALGORITHMS));
|
||||
(PrivilegedAction<String>) () -> Security.getProperty(SECURE_RANDOM_STRONG_ALGORITHMS));
|
||||
}
|
||||
|
||||
private static void setSecurityProperty(String datum) {
|
||||
AccessController.doPrivileged(
|
||||
(PrivilegedAction<Void>) () -> {
|
||||
Security.setProperty(
|
||||
SECURE_RANDOM_STRONG_ALGORITHMS, datum);
|
||||
return (Void) null;
|
||||
});
|
||||
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
|
||||
Security.setProperty(SECURE_RANDOM_STRONG_ALGORITHMS, datum);
|
||||
return (Void) null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
78
quarkus-tracing-service/pom.xml
Normal file
78
quarkus-tracing-service/pom.xml
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?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>ch.phoenix.oss</groupId>
|
||||
<artifactId>quarkus-commons</artifactId>
|
||||
<version>1.0.9-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>quarkus-tracing-service</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest-jackson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-opentelemetry</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-elytron-security-properties-file</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-config-yaml</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.rest-assured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>${jacoco-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>jacoco-check</id>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
<configuration>
|
||||
<dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
|
||||
<rules>
|
||||
<rule>
|
||||
<element>BUNDLE</element>
|
||||
<limits>
|
||||
<limit>
|
||||
<counter>INSTRUCTION</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.95</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,10 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.Converter;
|
||||
|
||||
public class LowerCaseStringConverter implements Converter<String> {
|
||||
@Override
|
||||
public String convert(String value) throws IllegalArgumentException, NullPointerException {
|
||||
return value.toLowerCase();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing;
|
||||
|
||||
import io.smallrye.config.ConfigMapping;
|
||||
import io.smallrye.config.WithConverter;
|
||||
import io.smallrye.config.WithDefault;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@ConfigMapping(prefix = "phoenix.commons.tracing")
|
||||
public interface TracingConfiguration {
|
||||
|
||||
RequestFilterConfiguration requestFilter();
|
||||
|
||||
interface RequestFilterConfiguration {
|
||||
|
||||
Headers headers();
|
||||
|
||||
interface Headers {
|
||||
|
||||
Set<String> DEFAULT_REDACTED = Set.of(HttpHeaders.AUTHORIZATION.toLowerCase());
|
||||
|
||||
/**
|
||||
* Optional set of headers to redact when tracing. By default, redacts
|
||||
* the 'Authorization' header.
|
||||
*
|
||||
* @return the set of headers to be redacted
|
||||
*/
|
||||
Optional<Set<@WithConverter(LowerCaseStringConverter.class) String>> redact();
|
||||
}
|
||||
|
||||
Path path();
|
||||
|
||||
interface Path {
|
||||
|
||||
@WithDefault("false")
|
||||
boolean includeRaw();
|
||||
}
|
||||
|
||||
Query query();
|
||||
|
||||
interface Query {
|
||||
|
||||
Set<String> DEFAULT_REDACTED = Set.of("access_token", "refresh_token", "apikey");
|
||||
|
||||
@WithDefault("false")
|
||||
boolean includeRaw();
|
||||
|
||||
/**
|
||||
* Optional set of query params to redact when tracing. By default, redacts
|
||||
* the following params: 'access_token', 'refresh_token' and 'apikey'.
|
||||
*
|
||||
* @return the set of query params to be redacted
|
||||
*/
|
||||
Optional<Set<@WithConverter(LowerCaseStringConverter.class) String>> redact();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing;
|
||||
|
||||
public class TracingConstants {
|
||||
|
||||
public static final String ACTOR = "actor";
|
||||
public static final String ANONYMOUS = "anonymous";
|
||||
public static final String REQUEST_ROUTE = "request.route";
|
||||
public static final String REQUEST_METHOD = "request.method";
|
||||
public static final String REQUEST_PATH_RAW = "request.path.raw";
|
||||
public static final String REQUEST_PATH_PARAMS = "request.path.params";
|
||||
public static final String REQUEST_QUERY_RAW = "request.query.raw";
|
||||
public static final String REQUEST_QUERY_PARAMS = "request.query.params";
|
||||
public static final String REQUEST_HEADERS = "request.headers";
|
||||
public static final String REQUEST_CLIENT_IP = "request.client.ip";
|
||||
public static final String SCHEDULER_JOB_NAME = "scheduler.job.name";
|
||||
|
||||
private TracingConstants() {}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing;
|
||||
|
||||
import io.quarkus.arc.Unremovable;
|
||||
import io.quarkus.logging.Log;
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.container.ContainerRequestContext;
|
||||
import jakarta.ws.rs.container.ResourceInfo;
|
||||
import java.util.List;
|
||||
import org.jboss.resteasy.reactive.server.ServerRequestFilter;
|
||||
|
||||
@Unremovable
|
||||
public class TracingRequestFilter {
|
||||
|
||||
private static final String REDACTED_VALUE = "********";
|
||||
|
||||
private final RoutingContext routingContext;
|
||||
private final TracingService tracingService;
|
||||
private final SecurityIdentity securityIdentity;
|
||||
private final TracingConfiguration configuration;
|
||||
|
||||
@Inject
|
||||
public TracingRequestFilter(
|
||||
RoutingContext routingContext,
|
||||
TracingService tracingService,
|
||||
SecurityIdentity securityIdentity,
|
||||
TracingConfiguration configuration) {
|
||||
this.routingContext = routingContext;
|
||||
this.tracingService = tracingService;
|
||||
this.securityIdentity = securityIdentity;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@ServerRequestFilter
|
||||
public void filter(ContainerRequestContext requestContext, ResourceInfo resourceInfo) {
|
||||
if (securityIdentity.isAnonymous()) {
|
||||
tracingService.trace(TracingConstants.ACTOR, TracingConstants.ANONYMOUS);
|
||||
} else {
|
||||
tracingService.trace(
|
||||
TracingConstants.ACTOR, securityIdentity.getPrincipal().getName());
|
||||
}
|
||||
|
||||
var method = requestContext.getMethod();
|
||||
tracingService.trace(TracingConstants.REQUEST_METHOD, method);
|
||||
|
||||
var routePattern = getRoutePattern(resourceInfo);
|
||||
tracingService.trace(TracingConstants.REQUEST_ROUTE, routePattern);
|
||||
|
||||
var uriInfo = requestContext.getUriInfo();
|
||||
uriInfo.getPathParameters()
|
||||
.forEach((key, value) ->
|
||||
tracingService.trace(TracingConstants.REQUEST_PATH_PARAMS + '.' + key, joinStrings(value)));
|
||||
|
||||
if (configuration.requestFilter().path().includeRaw()) {
|
||||
tracingService.trace(
|
||||
TracingConstants.REQUEST_PATH_RAW, uriInfo.getAbsolutePath().getRawPath());
|
||||
}
|
||||
|
||||
var redactedHeaders = configuration
|
||||
.requestFilter()
|
||||
.headers()
|
||||
.redact()
|
||||
.orElse(TracingConfiguration.RequestFilterConfiguration.Headers.DEFAULT_REDACTED);
|
||||
|
||||
requestContext.getHeaders().forEach((key, value) -> {
|
||||
var lowerCaseKey = key.toLowerCase();
|
||||
var property = TracingConstants.REQUEST_HEADERS + '.' + lowerCaseKey;
|
||||
if (redactedHeaders.contains(lowerCaseKey)) {
|
||||
tracingService.trace(property, REDACTED_VALUE);
|
||||
} else {
|
||||
tracingService.trace(property, joinStrings(value));
|
||||
}
|
||||
});
|
||||
|
||||
var redactedQueryParams = configuration
|
||||
.requestFilter()
|
||||
.query()
|
||||
.redact()
|
||||
.orElse(TracingConfiguration.RequestFilterConfiguration.Query.DEFAULT_REDACTED);
|
||||
|
||||
uriInfo.getQueryParameters().forEach((key, value) -> {
|
||||
var lowerCaseKey = key.toLowerCase();
|
||||
var property = TracingConstants.REQUEST_QUERY_PARAMS + '.' + lowerCaseKey;
|
||||
|
||||
if (redactedQueryParams.contains(lowerCaseKey)) {
|
||||
tracingService.trace(property, REDACTED_VALUE);
|
||||
} else {
|
||||
tracingService.trace(property, joinStrings(value));
|
||||
}
|
||||
});
|
||||
|
||||
if (configuration.requestFilter().query().includeRaw()) {
|
||||
var rawQuery = uriInfo.getRequestUri().getRawQuery();
|
||||
if (rawQuery != null && !rawQuery.isBlank()) {
|
||||
tracingService.trace(TracingConstants.REQUEST_QUERY_RAW, rawQuery);
|
||||
}
|
||||
}
|
||||
|
||||
tracingService.trace(
|
||||
TracingConstants.REQUEST_CLIENT_IP,
|
||||
routingContext.request().connection().remoteAddress(true).hostAddress());
|
||||
|
||||
Log.debugf("Incoming request: %s %s", method, uriInfo.getAbsolutePath().getRawPath());
|
||||
}
|
||||
|
||||
private static String joinStrings(List<String> value) {
|
||||
return String.join(", ", value);
|
||||
}
|
||||
|
||||
private String getRoutePattern(ResourceInfo resourceInfo) {
|
||||
String classPath = getPathValue(resourceInfo.getResourceClass().getAnnotation(Path.class));
|
||||
String methodPath = getPathValue(resourceInfo.getResourceMethod().getAnnotation(Path.class));
|
||||
|
||||
if (!classPath.isEmpty()) {
|
||||
if (methodPath.isEmpty()) {
|
||||
return "/" + classPath;
|
||||
} else {
|
||||
return "/" + classPath + "/" + methodPath;
|
||||
}
|
||||
} else {
|
||||
if (methodPath.isEmpty()) {
|
||||
return "/";
|
||||
} else {
|
||||
return "/" + methodPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getPathValue(Path path) {
|
||||
if (path == null) {
|
||||
return "";
|
||||
}
|
||||
return trimSlashes(path.value());
|
||||
}
|
||||
|
||||
private static String trimSlashes(String segment) {
|
||||
if (segment.isEmpty() || "/".equals(segment)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Assuming that it's not possible for a segment to contain //,
|
||||
// thus it's possible to avoid extra ifs and whiles, as well
|
||||
// as the use of regexes
|
||||
int start = (segment.charAt(0) == '/') ? 1 : 0;
|
||||
int end = (segment.charAt(segment.length() - 1) == '/') ? segment.length() - 1 : segment.length();
|
||||
|
||||
return segment.substring(start, end);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing;
|
||||
|
||||
public interface TracingService {
|
||||
|
||||
void clearAll();
|
||||
|
||||
void trace(String key, Object value);
|
||||
|
||||
String getActor();
|
||||
|
||||
String getRequestPathRaw();
|
||||
|
||||
String getRequestMethod();
|
||||
|
||||
String getRequestId();
|
||||
|
||||
String getTraceId();
|
||||
|
||||
String getSpanId();
|
||||
|
||||
String getClientIp();
|
||||
|
||||
String getSchedulerJob();
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing;
|
||||
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.quarkus.arc.DefaultBean;
|
||||
import io.quarkus.logging.Log;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import org.jboss.logging.MDC;
|
||||
|
||||
@DefaultBean
|
||||
@ApplicationScoped
|
||||
class TracingServiceImpl implements TracingService {
|
||||
|
||||
private final Span span;
|
||||
|
||||
TracingServiceImpl(Span span) {
|
||||
this.span = span;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAll() {
|
||||
MDC.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void trace(final String key, final Object value) {
|
||||
Log.tracef("tracing key=%s value=%s", key, value);
|
||||
MDC.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActor() {
|
||||
return (String) MDC.get(TracingConstants.ACTOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestPathRaw() {
|
||||
return (String) MDC.get(TracingConstants.REQUEST_PATH_RAW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestMethod() {
|
||||
return (String) MDC.get(TracingConstants.REQUEST_METHOD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestId() {
|
||||
return (String) MDC.get(TracingConstants.REQUEST_HEADERS + ".x-request-id");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTraceId() {
|
||||
return span.getSpanContext().getTraceId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSpanId() {
|
||||
return span.getSpanContext().getSpanId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientIp() {
|
||||
return (String) MDC.get(TracingConstants.REQUEST_CLIENT_IP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchedulerJob() {
|
||||
return (String) MDC.get(TracingConstants.SCHEDULER_JOB_NAME);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.startsWith;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.mockito.InjectSpy;
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class ActorTest {
|
||||
|
||||
@InjectSpy
|
||||
TracingService tracingService;
|
||||
|
||||
@Test
|
||||
void getAuthenticated() {
|
||||
var route = "/authenticated";
|
||||
RestAssured.given()
|
||||
.auth()
|
||||
.basic("jon", "doe")
|
||||
.accept(ContentType.TEXT)
|
||||
.when()
|
||||
.get(route)
|
||||
.then()
|
||||
.statusCode(200);
|
||||
|
||||
verify(tracingService).trace("actor", "jon");
|
||||
verify(tracingService).trace("request.method", "GET");
|
||||
verify(tracingService).trace("request.route", route);
|
||||
verify(tracingService).trace("request.headers.accept", "text/plain");
|
||||
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
|
||||
verify(tracingService).trace("request.headers.authorization", "********");
|
||||
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
|
||||
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
|
||||
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
|
||||
verify(tracingService).trace("request.client.ip", "127.0.0.1");
|
||||
verifyNoMoreInteractions(tracingService);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.startsWith;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.mockito.InjectSpy;
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class QueryParamTest {
|
||||
|
||||
@InjectSpy
|
||||
TracingService tracingService;
|
||||
|
||||
@Test
|
||||
void traceQueryParams() {
|
||||
var route = "/authenticated";
|
||||
RestAssured.given()
|
||||
.auth()
|
||||
.basic("jon", "doe")
|
||||
.accept(ContentType.TEXT)
|
||||
.header("X-SOMETHING-ELSE", "whatever")
|
||||
.queryParam("access_token", "api123")
|
||||
.queryParam("refresh_token", "refresh123")
|
||||
.queryParam("apikey", "apikey123")
|
||||
.queryParam("grant_type", "authorization_code")
|
||||
.when()
|
||||
.get(route)
|
||||
.then()
|
||||
.statusCode(200);
|
||||
|
||||
verify(tracingService).trace("actor", "jon");
|
||||
verify(tracingService).trace("request.method", "GET");
|
||||
verify(tracingService).trace("request.route", route);
|
||||
verify(tracingService).trace("request.headers.accept", "text/plain");
|
||||
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
|
||||
verify(tracingService).trace("request.headers.authorization", "********");
|
||||
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
|
||||
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
|
||||
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
|
||||
verify(tracingService).trace("request.headers.x-something-else", "whatever");
|
||||
verify(tracingService).trace("request.query.params.access_token", "********");
|
||||
verify(tracingService).trace("request.query.params.refresh_token", "********");
|
||||
verify(tracingService).trace("request.query.params.apikey", "********");
|
||||
verify(tracingService).trace("request.query.params.grant_type", "authorization_code");
|
||||
verify(tracingService).trace("request.client.ip", "127.0.0.1");
|
||||
verifyNoMoreInteractions(tracingService);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.startsWith;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.TestProfile;
|
||||
import io.quarkus.test.junit.mockito.InjectSpy;
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
@TestProfile(Test2Profile.class)
|
||||
class RawPathTest {
|
||||
|
||||
@InjectSpy
|
||||
TracingService tracingService;
|
||||
|
||||
@Test
|
||||
void getWithRawPath() {
|
||||
var route = "/no-leading-and-trailing/{param}/{param2}";
|
||||
RestAssured.given()
|
||||
.accept(ContentType.TEXT)
|
||||
.when()
|
||||
.get(route, 1, 2)
|
||||
.then()
|
||||
.statusCode(200);
|
||||
|
||||
verify(tracingService).trace("actor", "anonymous");
|
||||
verify(tracingService).trace("request.method", "GET");
|
||||
verify(tracingService).trace("request.route", route);
|
||||
verify(tracingService).trace("request.path.params.param", "1");
|
||||
verify(tracingService).trace("request.path.params.param2", "2");
|
||||
verify(tracingService).trace("request.path.raw", "/no-leading-and-trailing/1/2");
|
||||
verify(tracingService).trace("request.headers.accept", "text/plain");
|
||||
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
|
||||
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
|
||||
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
|
||||
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
|
||||
verify(tracingService).trace("request.client.ip", "127.0.0.1");
|
||||
verifyNoMoreInteractions(tracingService);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.startsWith;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.TestProfile;
|
||||
import io.quarkus.test.junit.mockito.InjectSpy;
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
@TestProfile(Test2Profile.class)
|
||||
class RedactedTest {
|
||||
|
||||
@InjectSpy
|
||||
TracingService tracingService;
|
||||
|
||||
@Test
|
||||
void traceRedactedValues() {
|
||||
var route = "/authenticated";
|
||||
RestAssured.given()
|
||||
.auth()
|
||||
.basic("jon", "doe")
|
||||
.accept(ContentType.TEXT)
|
||||
.header("X-SOMETHING-ELSE", "whatever")
|
||||
.queryParam("access_token", "api123")
|
||||
.queryParam("refresh_token", "refresh123")
|
||||
.queryParam("apikey", "apikey123")
|
||||
.queryParam("grant_type", "authorization_code")
|
||||
.when()
|
||||
.get(route)
|
||||
.then()
|
||||
.statusCode(200);
|
||||
|
||||
verify(tracingService).trace("actor", "jon");
|
||||
verify(tracingService).trace("request.method", "GET");
|
||||
verify(tracingService).trace("request.route", route);
|
||||
verify(tracingService).trace("request.path.raw", route);
|
||||
verify(tracingService).trace("request.headers.accept", "text/plain");
|
||||
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
|
||||
verify(tracingService).trace("request.headers.authorization", "********");
|
||||
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
|
||||
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
|
||||
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
|
||||
verify(tracingService).trace("request.headers.x-something-else", "********");
|
||||
verify(tracingService).trace("request.query.params.access_token", "********");
|
||||
verify(tracingService).trace("request.query.params.refresh_token", "refresh123");
|
||||
verify(tracingService).trace("request.query.params.apikey", "apikey123");
|
||||
verify(tracingService).trace("request.query.params.grant_type", "authorization_code");
|
||||
verify(tracingService)
|
||||
.trace(
|
||||
"request.query.raw",
|
||||
"access_token=api123&refresh_token=refresh123&apikey=apikey123&grant_type=authorization_code");
|
||||
verify(tracingService).trace("request.client.ip", "127.0.0.1");
|
||||
verifyNoMoreInteractions(tracingService);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing;
|
||||
|
||||
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.startsWith;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import io.quarkus.test.junit.mockito.InjectSpy;
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.http.ContentType;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
@QuarkusTest
|
||||
class RoutePatternTest {
|
||||
|
||||
@InjectSpy
|
||||
TracingService tracingService;
|
||||
|
||||
static Stream<Arguments> get() {
|
||||
return Stream.of(
|
||||
arguments("/", Map.of()),
|
||||
arguments("/leading-and-no-trailing", Map.of()),
|
||||
arguments("/leading/{param}/{param2}", Map.of("param", "1", "param2", "2")),
|
||||
arguments("/{param}/{param2}/trailing", Map.of("param", "1", "param2", "2")),
|
||||
arguments("/leading-and-no-trailing/{param}", Map.of("param", "1")),
|
||||
arguments("/leading-and-no-trailing/{param}/{param2}", Map.of("param", "1", "param2", "2")),
|
||||
arguments("/leading-and-trailing", Map.of()),
|
||||
arguments("/leading-and-trailing/{param}", Map.of("param", "1")),
|
||||
arguments("/leading-and-trailing/{param}/{param2}", Map.of("param", "1", "param2", "2")),
|
||||
arguments("/no-leading-and-no-trailing", Map.of()),
|
||||
arguments("/no-leading-and-no-trailing/{param}", Map.of("param", "1")),
|
||||
arguments("/no-leading-and-no-trailing/{param}/{param2}", Map.of("param", "1", "param2", "2")),
|
||||
arguments("/no-leading-and-trailing", Map.of()),
|
||||
arguments("/no-leading-and-trailing/{param}", Map.of("param", "1")),
|
||||
arguments("/no-leading-and-trailing/{param}/{param2}", Map.of("param", "1", "param2", "2")));
|
||||
}
|
||||
|
||||
@MethodSource
|
||||
@ParameterizedTest
|
||||
void get(String route, Map<String, String> pathParams) {
|
||||
RestAssured.given()
|
||||
.accept(ContentType.TEXT)
|
||||
.when()
|
||||
.get(route, pathParams)
|
||||
.then()
|
||||
.statusCode(200);
|
||||
|
||||
verify(tracingService).trace("actor", "anonymous");
|
||||
verify(tracingService).trace("request.method", "GET");
|
||||
verify(tracingService).trace("request.route", route);
|
||||
pathParams.forEach((key, value) -> verify(tracingService).trace("request.path.params." + key, value));
|
||||
verify(tracingService).trace("request.headers.accept", "text/plain");
|
||||
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
|
||||
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
|
||||
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
|
||||
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
|
||||
verify(tracingService).trace("request.client.ip", "127.0.0.1");
|
||||
verifyNoMoreInteractions(tracingService);
|
||||
}
|
||||
|
||||
@Test
|
||||
void postBlankResource() {
|
||||
var route = "/";
|
||||
RestAssured.given().accept(ContentType.TEXT).when().post(route).then().statusCode(200);
|
||||
|
||||
verify(tracingService).trace("actor", "anonymous");
|
||||
verify(tracingService).trace("request.method", "POST");
|
||||
verify(tracingService).trace("request.route", route);
|
||||
verify(tracingService).trace("request.headers.accept", "text/plain");
|
||||
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
|
||||
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
|
||||
verify(tracingService).trace("request.headers.content-length", "0");
|
||||
verify(tracingService)
|
||||
.trace("request.headers.content-type", "application/x-www-form-urlencoded; charset=ISO-8859-1");
|
||||
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
|
||||
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
|
||||
verify(tracingService).trace("request.client.ip", "127.0.0.1");
|
||||
verifyNoMoreInteractions(tracingService);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing;
|
||||
|
||||
import io.quarkus.test.junit.QuarkusTestProfile;
|
||||
|
||||
public class Test2Profile implements QuarkusTestProfile {
|
||||
|
||||
@Override
|
||||
public String getConfigProfile() {
|
||||
return "test2";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jboss.logging.MDC;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@QuarkusTest
|
||||
class TracingServiceImplTest {
|
||||
|
||||
@Inject
|
||||
TracingService tracingService;
|
||||
|
||||
@Inject
|
||||
Span span;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MDC.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getActor() {
|
||||
tracingService.trace("actor", "abc");
|
||||
assertThat(tracingService.getActor())
|
||||
.as("Actor should match expected value")
|
||||
.isEqualTo("abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRequestPathRaw() {
|
||||
tracingService.trace("request.path.raw", "/foo/bar");
|
||||
assertThat(tracingService.getRequestPathRaw())
|
||||
.as("Request Path Raw should match expected value")
|
||||
.isEqualTo("/foo/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRequestMethod() {
|
||||
tracingService.trace("request.method", "GET");
|
||||
assertThat(tracingService.getRequestMethod())
|
||||
.as("Request Method should match expected value")
|
||||
.isEqualTo("GET");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getRequestId() {
|
||||
tracingService.trace("request.headers.x-request-id", "ba458367-bfeb-46ba-87da-50b9343be8f9");
|
||||
assertThat(tracingService.getRequestId())
|
||||
.as("Request Id should match expected value")
|
||||
.isEqualTo("ba458367-bfeb-46ba-87da-50b9343be8f9");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithSpan
|
||||
void getTraceId() {
|
||||
assertThat(tracingService.getTraceId())
|
||||
.as("Request Trace Id should match expected value")
|
||||
.isEqualTo(span.getSpanContext().getTraceId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithSpan
|
||||
void getSpanId() {
|
||||
assertThat(tracingService.getSpanId())
|
||||
.as("Request Span Id should match expected value")
|
||||
.isEqualTo(span.getSpanContext().getSpanId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClientIp() {
|
||||
tracingService.trace("request.client.ip", "127.0.0.1");
|
||||
assertThat(tracingService.getClientIp())
|
||||
.as("Request Client Iü should match expected value")
|
||||
.isEqualTo("127.0.0.1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSchedulerJob() {
|
||||
tracingService.trace("scheduler.job.name", "scheduler/abc");
|
||||
assertThat(tracingService.getSchedulerJob())
|
||||
.as("Scheduler Job Name should match expected value")
|
||||
.isEqualTo("scheduler/abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearAll() {
|
||||
tracingService.trace("aaa", "bbb");
|
||||
assertThat(MDC.get("aaa")).isEqualTo("bbb");
|
||||
tracingService.clearAll();
|
||||
assertThat(MDC.get("aaa")).isNull();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing.resource;
|
||||
|
||||
import io.quarkus.security.Authenticated;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
|
||||
@Authenticated
|
||||
@Path("authenticated")
|
||||
public class AuthenticatedResource {
|
||||
|
||||
@GET
|
||||
public String get() {
|
||||
return "Hello user";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing.resource;
|
||||
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
@Path("")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public class BlankResource {
|
||||
|
||||
@GET
|
||||
@PermitAll
|
||||
public String get() {
|
||||
return "get";
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("")
|
||||
public String post() {
|
||||
return "post";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing.resource;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
@Path("/leading-and-no-trailing")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public class LeadingAndNoTrailingResource {
|
||||
|
||||
@GET
|
||||
@Path("")
|
||||
public String root() {
|
||||
return "root";
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{param}")
|
||||
public String singleParam(String param) {
|
||||
return param;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{param}/{param2}")
|
||||
public String multiParam(String param, String param2) {
|
||||
return param + "/" + param2;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing.resource;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
@Path("/leading-and-trailing/")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public class LeadingAndTrailingResource {
|
||||
|
||||
@GET
|
||||
@Path("/")
|
||||
public String root() {
|
||||
return "root";
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{param}/")
|
||||
public String singleParam(String param) {
|
||||
return param;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{param}/{param2}/")
|
||||
public String multiParam(String param, String param2) {
|
||||
return param + "/" + param2;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing.resource;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
@Path("no-leading-and-no-trailing")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public class NoLeadingAndNoTrailingResource {
|
||||
|
||||
@GET
|
||||
@Path("")
|
||||
public String root() {
|
||||
return "root";
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{param}")
|
||||
public String singleParam(String param) {
|
||||
return param;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{param}/{param2}")
|
||||
public String multiParam(String param, String param2) {
|
||||
return param + "/" + param2;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing.resource;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
@Path("no-leading-and-trailing/")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public class NoLeadingAndTrailingResource {
|
||||
|
||||
@GET
|
||||
@Path("/")
|
||||
public String root() {
|
||||
return "root";
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{param}/")
|
||||
public String singleParam(String param) {
|
||||
return param;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{param}/{param2}/")
|
||||
public String multiParam(String param, String param2) {
|
||||
return param + "/" + param2;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package ch.phoenix.oss.quarkus.commons.tracing.resource;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
|
||||
@Path("/")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
public class SlashResource {
|
||||
|
||||
@GET
|
||||
@Path("/leading/{param}/{param2}")
|
||||
public String doubleLeading(int param, int param2) {
|
||||
return "leading/" + param + "/" + param2;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{param}/{param2}/trailing/")
|
||||
public String doubleTrailing(int param, int param2) {
|
||||
return param + "/" + param2 + "/trailing";
|
||||
}
|
||||
}
|
39
quarkus-tracing-service/src/test/resources/application.yaml
Normal file
39
quarkus-tracing-service/src/test/resources/application.yaml
Normal file
|
@ -0,0 +1,39 @@
|
|||
quarkus:
|
||||
http:
|
||||
test-port: 0
|
||||
log:
|
||||
category:
|
||||
"org.apache.http.wire":
|
||||
level: DEBUG # set to DEBUG when RestAssured logs are necessary to understand test failures
|
||||
otel:
|
||||
traces:
|
||||
exporter: none
|
||||
security:
|
||||
users:
|
||||
embedded:
|
||||
enabled: true
|
||||
plain-text: true
|
||||
users:
|
||||
jon: doe
|
||||
|
||||
"%test2":
|
||||
quarkus:
|
||||
log:
|
||||
min-level: TRACE
|
||||
category:
|
||||
"ch.phoenix.oss.quarkus.commons.tracing":
|
||||
level: TRACE
|
||||
phoenix:
|
||||
commons:
|
||||
tracing:
|
||||
request-filter:
|
||||
path:
|
||||
include-raw: true
|
||||
headers:
|
||||
redact:
|
||||
- AUTHORIZATION
|
||||
- X-SOMETHING-ELSE
|
||||
query:
|
||||
include-raw: true
|
||||
redact:
|
||||
- ACCESS_TOKEN
|
|
@ -1,13 +1,11 @@
|
|||
<?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">
|
||||
<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>ch.phoenixtechnologies.quarkus</groupId>
|
||||
<groupId>ch.phoenix.oss</groupId>
|
||||
<artifactId>quarkus-commons</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<version>1.0.9-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>quarkus-uuid-generator</artifactId>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.uuid;
|
||||
package ch.phoenix.oss.quarkus.commons.uuid;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
@ -33,5 +33,4 @@ public interface UUIDGenerator {
|
|||
default UUID generateV5WithX500Namespace(String name) {
|
||||
return generateV5(NAMESPACE_X500, name);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.uuid;
|
||||
package ch.phoenix.oss.quarkus.commons.uuid;
|
||||
|
||||
import com.fasterxml.uuid.Generators;
|
||||
import io.quarkus.arc.DefaultBean;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -30,5 +29,4 @@ class UUIDGeneratorImpl implements UUIDGenerator {
|
|||
public UUID generateV5(UUID namespace, String name) {
|
||||
return Generators.nameBasedGenerator(namespace).generate(name);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +1,17 @@
|
|||
package ch.phoenixtechnologies.quarkus.commons.uuid;
|
||||
package ch.phoenix.oss.quarkus.commons.uuid;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
import ch.phoenixtechnologies.quarkus.commons.uuid.UUIDGeneratorImpl;
|
||||
import io.quarkus.test.junit.QuarkusTest;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
@QuarkusTest
|
||||
class UUIDGeneratorImplTest {
|
||||
|
||||
|
@ -44,9 +42,7 @@ class UUIDGeneratorImplTest {
|
|||
|
||||
var actual = generator.generateV4(getRandom());
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated UUID should match expected value")
|
||||
.isEqualTo(expected);
|
||||
assertThat(actual).as("Generated UUID should match expected value").isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -55,9 +51,7 @@ class UUIDGeneratorImplTest {
|
|||
|
||||
var actual = generator.generateV5("test");
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated UUID should match expected value")
|
||||
.isEqualTo(expected);
|
||||
assertThat(actual).as("Generated UUID should match expected value").isEqualTo(expected);
|
||||
}
|
||||
|
||||
public static Stream<Arguments> generateV5WithNamespace() {
|
||||
|
@ -65,8 +59,7 @@ class UUIDGeneratorImplTest {
|
|||
Arguments.of(NAMESPACE_DNS, V5_NAME, EXPECTED_V5_DNS),
|
||||
Arguments.of(NAMESPACE_URL, V5_NAME, EXPECTED_V5_URL),
|
||||
Arguments.of(NAMESPACE_OID, V5_NAME, EXPECTED_V5_OID),
|
||||
Arguments.of(NAMESPACE_X500, V5_NAME, EXPECTED_V5_X500)
|
||||
);
|
||||
Arguments.of(NAMESPACE_X500, V5_NAME, EXPECTED_V5_X500));
|
||||
}
|
||||
|
||||
@MethodSource
|
||||
|
@ -74,27 +67,21 @@ class UUIDGeneratorImplTest {
|
|||
void generateV5WithNamespace(UUID namespace, String name, UUID expected) {
|
||||
var actual = generator.generateV5(namespace, name);
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated UUID should match expected value")
|
||||
.isEqualTo(expected);
|
||||
assertThat(actual).as("Generated UUID should match expected value").isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateV5WithDNSNamespace() {
|
||||
var actual = generator.generateV5WithDNSNamespace(V5_NAME);
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated UUID should match expected value")
|
||||
.isEqualTo(EXPECTED_V5_DNS);
|
||||
assertThat(actual).as("Generated UUID should match expected value").isEqualTo(EXPECTED_V5_DNS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateV5WithURLNamespace() {
|
||||
var actual = generator.generateV5WithURLNamespace(V5_NAME);
|
||||
|
||||
assertThat(actual)
|
||||
.as("Generated UUID should match expected value")
|
||||
.isEqualTo(EXPECTED_V5_URL);
|
||||
assertThat(actual).as("Generated UUID should match expected value").isEqualTo(EXPECTED_V5_URL);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -114,5 +101,4 @@ class UUIDGeneratorImplTest {
|
|||
private static Random getRandom() {
|
||||
return new Random(24353L);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue