mirror of
https://github.com/Alfresco/acs-community-packaging.git
synced 2026-09-16 18:13:21 +00:00
Compare commits
461
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2227c522d1 | ||
|
|
c280cb76ec | ||
|
|
6c1b00a11d | ||
|
|
06a79d7fe8 | ||
|
|
b269c4ff11 | ||
|
|
f0f85dd031 | ||
|
|
db9ae0a800 | ||
|
|
81df71adcb | ||
|
|
75d57e4806 | ||
|
|
92b39d478f | ||
|
|
5c84333cc2 | ||
|
|
29338fd53e | ||
|
|
0e5c7c9465 | ||
|
|
52832f1100 | ||
|
|
b7afbb57d6 | ||
|
|
4e1f805bff | ||
|
|
6183411b72 | ||
|
|
691fee053f | ||
|
|
2658664376 | ||
|
|
a4d1b7c37f | ||
|
|
5f1ba33c6b | ||
|
|
7c0662abe7 | ||
|
|
0ade3eb669 | ||
|
|
f1bae17140 | ||
|
|
b64c65f000 | ||
|
|
035fd9ca7c | ||
|
|
3dc624a652 | ||
|
|
60bf0b5b14 | ||
|
|
6fbab4c900 | ||
|
|
d6dd22a2a0 | ||
|
|
f9d8167d35 | ||
|
|
bf2e0cc91a | ||
|
|
5d1ee8c4c0 | ||
|
|
60a470da91 | ||
|
|
9ef8d49f80 | ||
|
|
2ed27931a5 | ||
|
|
5af1ad824e | ||
|
|
975646f422 | ||
|
|
32d42e8d5f | ||
|
|
c644f08501 | ||
|
|
fd4c548e4b | ||
|
|
f01f4636ba | ||
|
|
274278dbeb | ||
|
|
fa2868c092 | ||
|
|
2d4a251226 | ||
|
|
157e5a2553 | ||
|
|
2b6cc0d8b2 | ||
|
|
1f36d3eeb5 | ||
|
|
ddcfbf0510 | ||
|
|
1175ea568b | ||
|
|
c638d28cba | ||
|
|
56db329fc4 | ||
|
|
e1c2d88d2b | ||
|
|
4e2eec974d | ||
|
|
a651c90dd4 | ||
|
|
04b717ba95 | ||
|
|
72338d4243 | ||
|
|
9ed1edf334 | ||
|
|
240071c9be | ||
|
|
0b715d5596 | ||
|
|
5e8864c4ba | ||
|
|
a72ac09968 | ||
|
|
ce7197cc08 | ||
|
|
0a92a00e4f | ||
|
|
12774a7d22 | ||
|
|
db91c6c4d1 | ||
|
|
9a9839bf4d | ||
|
|
e595960521 | ||
|
|
e4f5ce6ee6 | ||
|
|
b93a4418cb | ||
|
|
e8b295f679 | ||
|
|
ed6dd74864 | ||
|
|
b556a55117 | ||
|
|
aeb1e994d2 | ||
|
|
f14138bbec | ||
|
|
99102b597e | ||
|
|
4d7c1964ec | ||
|
|
19bcf20933 | ||
|
|
35f280eae5 | ||
|
|
dc0e49706e | ||
|
|
dff2e08dd2 | ||
|
|
7b18ea984a | ||
|
|
5b208509a0 | ||
|
|
a2061e0731 | ||
|
|
267f9f988e | ||
|
|
f00e8f014b | ||
|
|
6ae6140e28 | ||
|
|
a7b2090893 | ||
|
|
c58dd8bd50 | ||
|
|
09eacc4294 | ||
|
|
9bd65dbc7e | ||
|
|
555c3db905 | ||
|
|
350b5fb159 | ||
|
|
5c45fe3faa | ||
|
|
b1ee5b8014 | ||
|
|
1ce3f6c150 | ||
|
|
ae5b3fa58a | ||
|
|
853e1a63bf | ||
|
|
bb0854c36e | ||
|
|
0b295bd3f7 | ||
|
|
08d8eb19d5 | ||
|
|
ab6bcf90d1 | ||
|
|
9fb8af9192 | ||
|
|
0e05048b79 | ||
|
|
ef6ae66a60 | ||
|
|
b37e4405a3 | ||
|
|
1451d7ef7a | ||
|
|
bda880ca42 | ||
|
|
55193a915b | ||
|
|
4e564a942a | ||
|
|
f80bfa17b6 | ||
|
|
776e79135a | ||
|
|
20cb9e856b | ||
|
|
5f8717652c | ||
|
|
ee689aba90 | ||
|
|
46eac6d5b9 | ||
|
|
351816775d | ||
|
|
a0f498e8d2 | ||
|
|
c8489fe6c7 | ||
|
|
d3aeac4a9d | ||
|
|
2b61e37be3 | ||
|
|
262573c6b7 | ||
|
|
969683c33b | ||
|
|
ba3fb7eec4 | ||
|
|
011e41a20d | ||
|
|
8736aea464 | ||
|
|
c79250581c | ||
|
|
d4d2a5b15b | ||
|
|
ed0008c5cd | ||
|
|
9cce32fc1f | ||
|
|
751b1d4c14 | ||
|
|
3e404f7a3b | ||
|
|
023968ca5e | ||
|
|
673330dc5f | ||
|
|
0ad1e5b653 | ||
|
|
1196038009 | ||
|
|
16589bfa17 | ||
|
|
be0264f5a2 | ||
|
|
b36f5032e9 | ||
|
|
d72064db50 | ||
|
|
df9d489142 | ||
|
|
5b0c9f3996 | ||
|
|
7731a4aa1a | ||
|
|
f082e00736 | ||
|
|
2928545f0b | ||
|
|
de15826ca2 | ||
|
|
a93f7e5dee | ||
|
|
0e2388ce8a | ||
|
|
37cdf77375 | ||
|
|
4b4a30a154 | ||
|
|
782454e3fd | ||
|
|
29fa03aba0 | ||
|
|
a3c4af776f | ||
|
|
a33ef36bd6 | ||
|
|
ea3b050ed6 | ||
|
|
e17e78a5d6 | ||
|
|
7f3a6133bc | ||
|
|
5240a04a68 | ||
|
|
f13ca82499 | ||
|
|
8133e5975c | ||
|
|
8cbc850861 | ||
|
|
8b3d450452 | ||
|
|
f7725392d9 | ||
|
|
e44ce19320 | ||
|
|
71ff372afc | ||
|
|
caea7b4efb | ||
|
|
ac323556b5 | ||
|
|
421022a576 | ||
|
|
2a0b98e423 | ||
|
|
4cc4adf61f | ||
|
|
bc308f3615 | ||
|
|
c5bf783bca | ||
|
|
a0531edf72 | ||
|
|
b85ebfa4c1 | ||
|
|
56ea91fa5b | ||
|
|
0ec0dc3043 | ||
|
|
73acd243e8 | ||
|
|
5f339944a3 | ||
|
|
8872f9634a | ||
|
|
7db6d945d6 | ||
|
|
630b48ffdf | ||
|
|
36a99c4587 | ||
|
|
485270a3b2 | ||
|
|
962e79f052 | ||
|
|
af7ef7286b | ||
|
|
e54a70ff32 | ||
|
|
7f949924c3 | ||
|
|
0045df452f | ||
|
|
89fdb0acaf | ||
|
|
50e3f88d97 | ||
|
|
68d49a81f8 | ||
|
|
8ec63574a3 | ||
|
|
1031230f1f | ||
|
|
6b3b16a9a4 | ||
|
|
9892d3705a | ||
|
|
06ead549e3 | ||
|
|
360c584f64 | ||
|
|
3bb5a988cf | ||
|
|
3442fce59a | ||
|
|
f83f55060a | ||
|
|
1e8dd2817a | ||
|
|
17a38d51d0 | ||
|
|
25af309ea9 | ||
|
|
c37e9526c6 | ||
|
|
29720e6e64 | ||
|
|
26fbca754f | ||
|
|
a04fbabee9 | ||
|
|
a142b96bdf | ||
|
|
a3fb8d3637 | ||
|
|
c19bcdb4fc | ||
|
|
b54d674d5b | ||
|
|
a60bde4b45 | ||
|
|
82323f0a95 | ||
|
|
d44d995a10 | ||
|
|
87cb633a4b | ||
|
|
d7bc35f807 | ||
|
|
2a93669740 | ||
|
|
174ab3349f | ||
|
|
999bfa4f46 | ||
|
|
a719ab4fa9 | ||
|
|
dfe9b7a59e | ||
|
|
e77852afea | ||
|
|
f97f9a0a91 | ||
|
|
b9d9ae55d3 | ||
|
|
8dc54096e3 | ||
|
|
5b6c11b730 | ||
|
|
c6b699eb64 | ||
|
|
b9a034a276 | ||
|
|
ca4f8c68be | ||
|
|
140157277f | ||
|
|
734a476ece | ||
|
|
6252de4707 | ||
|
|
3c34c532d0 | ||
|
|
b1b5c9f660 | ||
|
|
ba43305169 | ||
|
|
bd8d35fbb0 | ||
|
|
e4ac865301 | ||
|
|
ce91803b7a | ||
|
|
adeab43b5a | ||
|
|
c8d47f10f6 | ||
|
|
ff9c75bc55 | ||
|
|
24db2ecd16 | ||
|
|
bea8523556 | ||
|
|
9c863ba2a0 | ||
|
|
63de82aea2 | ||
|
|
5d512ed857 | ||
|
|
bc9ec03949 | ||
|
|
495266b3d7 | ||
|
|
fbb9ef7e16 | ||
|
|
4a8d7cf5b2 | ||
|
|
9345081327 | ||
|
|
9d810abaa2 | ||
|
|
e07f6f2f60 | ||
|
|
be5b82fc1c | ||
|
|
8487068b03 | ||
|
|
9764e292a9 | ||
|
|
79ca33f16b | ||
|
|
e61f0e6acd | ||
|
|
62c11b4ba3 | ||
|
|
14eb024001 | ||
|
|
d7880064d5 | ||
|
|
10c9b11f21 | ||
|
|
8c096b7203 | ||
|
|
ccc1ff32b0 | ||
|
|
e85aa18487 | ||
|
|
bd41276881 | ||
|
|
34a495a878 | ||
|
|
a08cf71ca7 | ||
|
|
e2d23cda41 | ||
|
|
d9a26c4f04 | ||
|
|
42449efc05 | ||
|
|
3c72804cb2 | ||
|
|
2a1618eed1 | ||
|
|
8dae776c87 | ||
|
|
a7e3eb01fb | ||
|
|
1db23f2e7d | ||
|
|
d917a6856f | ||
|
|
aa086100c4 | ||
|
|
a28f7a2e4e | ||
|
|
f46a4e15bc | ||
|
|
ecdfd47ce7 | ||
|
|
fd4a4223df | ||
|
|
2b051a5859 | ||
|
|
72f48d5a02 | ||
|
|
a4e19d9a76 | ||
|
|
95b320258f | ||
|
|
7707fdf552 | ||
|
|
c41960fb4e | ||
|
|
ae77b1eb49 | ||
|
|
badf8fdd13 | ||
|
|
5eb021421c | ||
|
|
88ff9f0411 | ||
|
|
224a922ecb | ||
|
|
34ba3a9845 | ||
|
|
db207e34f8 | ||
|
|
723928d02a | ||
|
|
b987a3da83 | ||
|
|
54d6c589cb | ||
|
|
60af28d6f7 | ||
|
|
df073bb29e | ||
|
|
381cc7fef5 | ||
|
|
ae0f441388 | ||
|
|
db773ab6dc | ||
|
|
915f05565d | ||
|
|
eb0c971daf | ||
|
|
2346357bc1 | ||
|
|
cd5fdf55e4 | ||
|
|
a0dc27fafd | ||
|
|
527c24d597 | ||
|
|
b8ad8bf266 | ||
|
|
847a6b5a27 | ||
|
|
e728eecc33 | ||
|
|
326de0cddf | ||
|
|
6dc44e028f | ||
|
|
ec185bfefa | ||
|
|
912657dcf4 | ||
|
|
7e0c38d097 | ||
|
|
4dbe6b8c98 | ||
|
|
8df9f7bdf5 | ||
|
|
97cca8240c | ||
|
|
6aa8bcc853 | ||
|
|
7fcf8da378 | ||
|
|
4feef848b8 | ||
|
|
5321cc0ca6 | ||
|
|
a7685e6c86 | ||
|
|
a75c2f286f | ||
|
|
05349fd5f4 | ||
|
|
bbfc00c39c | ||
|
|
e2e1e8e7c1 | ||
|
|
65bd40e77e | ||
|
|
52d7524868 | ||
|
|
81023c8e99 | ||
|
|
697f2820c1 | ||
|
|
2a555e44ec | ||
|
|
6d8f528ee4 | ||
|
|
32dbf00695 | ||
|
|
e9d741b411 | ||
|
|
088c159ba4 | ||
|
|
cb185f90d1 | ||
|
|
f36db37acd | ||
|
|
75d5d39208 | ||
|
|
d0c6c8b8b4 | ||
|
|
bd0cac80ac | ||
|
|
4031b67417 | ||
|
|
f2aa80352b | ||
|
|
26624ece0b | ||
|
|
86ac11e74e | ||
|
|
62abf6c0d3 | ||
|
|
96afa81b30 | ||
|
|
726ec9715b | ||
|
|
3a6eabb2b0 | ||
|
|
b9175cda67 | ||
|
|
d8e74c9a66 | ||
|
|
0d2508cfd8 | ||
|
|
0602443410 | ||
|
|
bbf45555f9 | ||
|
|
1a4161bc4e | ||
|
|
0a8349beac | ||
|
|
600292cab8 | ||
|
|
bc6e6e6fe4 | ||
|
|
c36870e49c | ||
|
|
3a381a3425 | ||
|
|
db50d96160 | ||
|
|
ce2d1d0a3b | ||
|
|
affa2b3fd1 | ||
|
|
c1784b388f | ||
|
|
0e4b6df7af | ||
|
|
3f0a7757eb | ||
|
|
a4d0e78887 | ||
|
|
898e4e2ad7 | ||
|
|
0a6521de02 | ||
|
|
05bdb689f2 | ||
|
|
6cc270264b | ||
|
|
57b7fe7d65 | ||
|
|
fd4f1f24b0 | ||
|
|
bac86e7ca1 | ||
|
|
8c94e339dd | ||
|
|
07a5f7f88f | ||
|
|
544165e172 | ||
|
|
ee13662274 | ||
|
|
db8676b73e | ||
|
|
b363c0341c | ||
|
|
8606ff3aa2 | ||
|
|
c7625052ba | ||
|
|
eb22a74f1f | ||
|
|
a858c7046e | ||
|
|
5944ef48c4 | ||
|
|
f5aa0c93e6 | ||
|
|
90ffa23561 | ||
|
|
cca10c3bd4 | ||
|
|
3a8c21ae48 | ||
|
|
dfdfbed093 | ||
|
|
b302eb9714 | ||
|
|
dd9cb21d5c | ||
|
|
0dd61c2e0d | ||
|
|
b5d253d6db | ||
|
|
da4c4e24c5 | ||
|
|
32ae60eadf | ||
|
|
7d28684e55 | ||
|
|
7eaffbed80 | ||
|
|
ee5830808e | ||
|
|
72ca0e34a1 | ||
|
|
3106d3e7be | ||
|
|
c1a7f6ffb0 | ||
|
|
d4f0006825 | ||
|
|
5af41219a3 | ||
|
|
e5031aadb4 | ||
|
|
e8b9b64afc | ||
|
|
a9975152b7 | ||
|
|
db5a85fc43 | ||
|
|
fcedcd878b | ||
|
|
a58cc3dc72 | ||
|
|
e699b6cce5 | ||
|
|
3e82bb8707 | ||
|
|
511d5cafdb | ||
|
|
23b1718396 | ||
|
|
9a70909b6b | ||
|
|
d652dc6d66 | ||
|
|
c27b8dfeed | ||
|
|
5532c6bc57 | ||
|
|
10fbf62e00 | ||
|
|
0a3e5c9770 | ||
|
|
15bdd187c8 | ||
|
|
cb8ec03d95 | ||
|
|
718f352838 | ||
|
|
c7794747ee | ||
|
|
b7ce513320 | ||
|
|
da02d4d273 | ||
|
|
22a27773fd | ||
|
|
c94e81c198 | ||
|
|
5aa54969af | ||
|
|
92b5a9b8e4 | ||
|
|
c8ad52884a | ||
|
|
2dd77da097 | ||
|
|
dc8f9bf4ea | ||
|
|
946ad7b9bd | ||
|
|
77100a81dc | ||
|
|
71cefc3d9a | ||
|
|
5b6b23f656 | ||
|
|
1d6460398a | ||
|
|
0fcef3cca8 | ||
|
|
4fb77ddba8 | ||
|
|
e7226b42d4 | ||
|
|
8ec1a1a9b5 | ||
|
|
0e89c23b14 | ||
|
|
3213ce72c5 | ||
|
|
ce62a721e4 | ||
|
|
5600bcc5ef | ||
|
|
7d7cc3cf0a | ||
|
|
806e7cbc3a | ||
|
|
f766f25d97 | ||
|
|
a4449af635 | ||
|
|
0791f53e89 | ||
|
|
56f76fba19 | ||
|
|
f5ad17a9a0 | ||
|
|
9119a35939 | ||
|
|
8efa004e28 | ||
|
|
58751b98d8 | ||
|
|
f0a77ca965 | ||
|
|
7da64a6ac2 | ||
|
|
d7c09e862a |
+11
-2
@@ -12,8 +12,9 @@ updates:
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "22:00"
|
||||
timezone: Africa/Abidjan
|
||||
open-pull-requests-limit: 99
|
||||
timezone: "Europe/London"
|
||||
# disabling versions updates, when set to 0 only security updates will open
|
||||
open-pull-requests-limit: 0
|
||||
ignore:
|
||||
# Upstream alfresco-community-repo artifacts
|
||||
- dependency-name: org.alfresco:alfresco-community-repo
|
||||
@@ -41,3 +42,11 @@ updates:
|
||||
- ">= 3.a, < 4"
|
||||
registries:
|
||||
- maven-repository-artifacts-alfresco-com-nexus-content-groups-int
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
|
||||
+125
-26
@@ -16,6 +16,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
JAVA_VERSION: "21"
|
||||
GIT_USERNAME: ${{ secrets.BOT_GITHUB_USERNAME }}
|
||||
GIT_EMAIL: ${{ secrets.BOT_GITHUB_EMAIL }}
|
||||
GIT_PASSWORD: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
@@ -29,10 +30,24 @@ env:
|
||||
TAS_ENVIRONMENT: ./tests/environment
|
||||
TAS_SCRIPTS: ../alfresco-community-repo/packaging/tests/scripts
|
||||
# Release version has to start with real version (23.2.0-....) for the docker image to build successfully.
|
||||
RELEASE_VERSION: 23.2.0-A12
|
||||
DEVELOPMENT_VERSION: 23.2.0-A13-SNAPSHOT
|
||||
RELEASE_VERSION: 26.2.0-A.30
|
||||
DEVELOPMENT_VERSION: 26.2.0-A.31-SNAPSHOT
|
||||
|
||||
jobs:
|
||||
precommit:
|
||||
name: Pre-Commit checks
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
!contains(github.event.head_commit.message, '[skip tests]')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
with:
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/pre-commit@v8.16.0
|
||||
|
||||
tas_tests:
|
||||
name: ${{ matrix.testSuite }} TAS tests
|
||||
runs-on: ubuntu-latest
|
||||
@@ -78,9 +93,11 @@ jobs:
|
||||
profiles: all-tas-tests
|
||||
compose-file: docker-compose-minimal.yml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.33.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.33.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
with:
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
- name: "Init"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -95,14 +112,90 @@ jobs:
|
||||
- name: "Clean Maven cache"
|
||||
run: bash ./scripts/ci/cleanup_cache.sh
|
||||
|
||||
tas_tests_search_api:
|
||||
name: ${{ matrix.testSuite }} | TAS tests (Search API)
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
(github.ref_name == 'master' || startsWith(github.ref_name, 'release/') || github.event_name == 'pull_request') &&
|
||||
!contains(github.event.head_commit.message, '[skip search]') &&
|
||||
!contains(github.event.head_commit.message, '[skip tests]')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- testSuite: Elasticsearch postgreSQL
|
||||
profiles: all-tas-tests,elastic
|
||||
search-engine-type: elasticsearch
|
||||
db-type: postgresql
|
||||
- testSuite: Elasticsearch MySQL
|
||||
profiles: all-tas-tests,elastic
|
||||
search-engine-type: elasticsearch
|
||||
db-type: mysql
|
||||
- testSuite: Elasticsearch Maria DB 11.8
|
||||
profiles: all-tas-tests,elastic
|
||||
search-engine-type: elasticsearch
|
||||
db-type: mariadb
|
||||
- testSuite: Opensearch
|
||||
profiles: all-tas-tests,elastic
|
||||
search-engine-type: opensearch
|
||||
db-type: postgresql
|
||||
- testSuite: Elasticsearch Basic Auth postgreSQL
|
||||
profiles: all-tas-tests,elastic-basic-auth
|
||||
search-engine-type: elasticsearch
|
||||
db-type: postgresql
|
||||
- testSuite: Elasticsearch Basic Auth MySQL
|
||||
profiles: all-tas-tests,elastic-basic-auth
|
||||
search-engine-type: elasticsearch
|
||||
db-type: mysql
|
||||
- testSuite: Elasticsearch Basic Auth MariaDB 11.8
|
||||
profiles: all-tas-tests,elastic-basic-auth
|
||||
search-engine-type: elasticsearch
|
||||
db-type: mariadb
|
||||
- testSuite: Opensearch Basic Auth
|
||||
profiles: all-tas-tests,elastic-basic-auth
|
||||
search-engine-type: opensearch
|
||||
db-type: postgresql
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v12.4.3
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v12.4.3
|
||||
with:
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
bash ./scripts/ci/init.sh
|
||||
bash ./scripts/ci/build.sh
|
||||
- name: "Run tests"
|
||||
id: tests
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
set -a
|
||||
source ${TAS_ENVIRONMENT}/.env
|
||||
set +a
|
||||
BATCH_INDEXING_TAG="${BATCH_INDEXING_TAG//[[:space:]]/}"
|
||||
mvn -B -ntp -q install -N -f tests/pom.xml
|
||||
mvn -B -ntp -q install -DskipTests -f tests/testcontainers-env/pom.xml
|
||||
mvn -B install -ntp -f tests/tas-elasticsearch/pom.xml -P${{ matrix.profiles }} -Denvironment=default -DrunBugs=false "-Dsearch.engine.type=${{ matrix.search-engine-type }}" "-Ddatabase.type=${{ matrix.db-type }}" -Dindeximage="alfresco/alfresco-elasticsearch-batch-indexing:${BATCH_INDEXING_TAG}" -Drepoimage="alfresco-repository-databases:latest"
|
||||
- name: "Dump all Docker containers logs"
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/docker-dump-containers-logs@v12.4.3
|
||||
if: failure() && steps.tests.outcome == 'failure'
|
||||
- name: "Clean Maven cache"
|
||||
run: bash ./scripts/ci/cleanup_cache.sh
|
||||
|
||||
|
||||
community_zip_tests:
|
||||
name: Community Distribution Zip content tests
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, '[skip tests]')"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.33.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.33.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
with:
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
- name: "Init"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -122,12 +215,14 @@ jobs:
|
||||
if: >
|
||||
!contains(github.event.head_commit.message, '[skip tests]')
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.35.2
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.35.2
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
with:
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -162,12 +257,14 @@ jobs:
|
||||
ports:
|
||||
- 5000:5000
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.33.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.33.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
with:
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: "Init"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -194,14 +291,16 @@ jobs:
|
||||
ports:
|
||||
- 5000:5000
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.33.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v1.35.2
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v1.33.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
with:
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: "Init"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -216,7 +315,7 @@ jobs:
|
||||
- name: "Clean Maven cache"
|
||||
run: bash ./scripts/ci/cleanup_cache.sh
|
||||
- name: "Configure AWS credentials"
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_S3_STAGING_ACCESS_KEY }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_S3_STAGING_SECRET_KEY }}
|
||||
@@ -237,10 +336,10 @@ jobs:
|
||||
(github.ref_name == 'master' || startsWith(github.ref_name, 'release/')) &&
|
||||
github.event_name != 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.33.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- name: "Configure AWS credentials"
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_S3_RELEASE_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_S3_RELEASE_SECRET_ACCESS_KEY }}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Pre-Commit formatter
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- precommit/**
|
||||
|
||||
env:
|
||||
JAVA_VERSION: "21"
|
||||
MAVEN_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
|
||||
MAVEN_USERNAME: ${{ secrets.NEXUS_USERNAME }}
|
||||
|
||||
jobs:
|
||||
format-code:
|
||||
name: "Reformat code"
|
||||
runs-on: ubuntu-latest
|
||||
if: contains(github.event.head_commit.message, '[reformat code]')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
with:
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
|
||||
continue-on-error: true
|
||||
with:
|
||||
extra_args: --all-files
|
||||
- name: Update secrets baseline
|
||||
run: pip install detect-secrets && detect-secrets scan --baseline .secrets.baseline
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/git-commit-changes@v8.16.0
|
||||
with:
|
||||
username: ${{ secrets.BOT_GITHUB_USERNAME }}
|
||||
add-options: -u
|
||||
commit-message: "Apply Pre-Commit code formatting"
|
||||
skip-if-no-changes: true
|
||||
- name: Push changes
|
||||
run: git push
|
||||
@@ -0,0 +1,14 @@
|
||||
repos:
|
||||
- repo: https://github.com/Yelp/detect-secrets
|
||||
rev: v1.5.0
|
||||
hooks:
|
||||
- id: detect-secrets
|
||||
args: ["--baseline", ".secrets.baseline"]
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: check-format-and-headers
|
||||
name: Check format and headers and fix if necessary
|
||||
entry: ./scripts/hooks/check-format-and-headers.sh
|
||||
language: script
|
||||
files: ".*.java"
|
||||
pass_filenames: false
|
||||
@@ -0,0 +1,292 @@
|
||||
{
|
||||
"version": "1.5.0",
|
||||
"plugins_used": [
|
||||
{
|
||||
"name": "ArtifactoryDetector"
|
||||
},
|
||||
{
|
||||
"name": "AWSKeyDetector"
|
||||
},
|
||||
{
|
||||
"name": "AzureStorageKeyDetector"
|
||||
},
|
||||
{
|
||||
"name": "Base64HighEntropyString",
|
||||
"limit": 4.5
|
||||
},
|
||||
{
|
||||
"name": "BasicAuthDetector"
|
||||
},
|
||||
{
|
||||
"name": "CloudantDetector"
|
||||
},
|
||||
{
|
||||
"name": "DiscordBotTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "GitHubTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "GitLabTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "HexHighEntropyString",
|
||||
"limit": 3.0
|
||||
},
|
||||
{
|
||||
"name": "IbmCloudIamDetector"
|
||||
},
|
||||
{
|
||||
"name": "IbmCosHmacDetector"
|
||||
},
|
||||
{
|
||||
"name": "IPPublicDetector"
|
||||
},
|
||||
{
|
||||
"name": "JwtTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "KeywordDetector",
|
||||
"keyword_exclude": ""
|
||||
},
|
||||
{
|
||||
"name": "MailchimpDetector"
|
||||
},
|
||||
{
|
||||
"name": "NpmDetector"
|
||||
},
|
||||
{
|
||||
"name": "OpenAIDetector"
|
||||
},
|
||||
{
|
||||
"name": "PrivateKeyDetector"
|
||||
},
|
||||
{
|
||||
"name": "PypiTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "SendGridDetector"
|
||||
},
|
||||
{
|
||||
"name": "SlackDetector"
|
||||
},
|
||||
{
|
||||
"name": "SoftlayerDetector"
|
||||
},
|
||||
{
|
||||
"name": "SquareOAuthDetector"
|
||||
},
|
||||
{
|
||||
"name": "StripeDetector"
|
||||
},
|
||||
{
|
||||
"name": "TelegramBotTokenDetector"
|
||||
},
|
||||
{
|
||||
"name": "TwilioKeyDetector"
|
||||
}
|
||||
],
|
||||
"filters_used": [
|
||||
{
|
||||
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.common.is_baseline_file",
|
||||
"filename": ".secrets.baseline"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
|
||||
"min_level": 2
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_lock_file"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_sequential_string"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_swagger_file"
|
||||
},
|
||||
{
|
||||
"path": "detect_secrets.filters.heuristic.is_templated_secret"
|
||||
}
|
||||
],
|
||||
"results": {
|
||||
"dev/dev-tomcat/src/main/tomcat/shared/classes/alfresco-global.properties": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "dev/dev-tomcat/src/main/tomcat/shared/classes/alfresco-global.properties",
|
||||
"hashed_secret": "a4a747bd4ba5e3a5049cad116881867c71fb625b",
|
||||
"is_verified": false,
|
||||
"line_number": 8
|
||||
}
|
||||
],
|
||||
"dev/dev-tomcat/src/main/tomcat/tomcat-users.xml": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "dev/dev-tomcat/src/main/tomcat/tomcat-users.xml",
|
||||
"hashed_secret": "2be88ca4242c76e8253ac62474851065032d6833",
|
||||
"is_verified": false,
|
||||
"line_number": 19
|
||||
}
|
||||
],
|
||||
"dev/docker-compose.yml": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "dev/docker-compose.yml",
|
||||
"hashed_secret": "d1ebbc78705ad98aa2c8fb357d2e30c8f001673a",
|
||||
"is_verified": false,
|
||||
"line_number": 41
|
||||
}
|
||||
],
|
||||
"tests/environment/docker-compose-minimal+transforms.yml": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/environment/docker-compose-minimal+transforms.yml",
|
||||
"hashed_secret": "9e3d103f7aa5f4f778cf752087dfceeba15d4fef",
|
||||
"is_verified": false,
|
||||
"line_number": 13
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/environment/docker-compose-minimal+transforms.yml",
|
||||
"hashed_secret": "92cda941553ea9ba9b39716c79a46b08a740613c",
|
||||
"is_verified": false,
|
||||
"line_number": 23
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/environment/docker-compose-minimal+transforms.yml",
|
||||
"hashed_secret": "d1ebbc78705ad98aa2c8fb357d2e30c8f001673a",
|
||||
"is_verified": false,
|
||||
"line_number": 76
|
||||
}
|
||||
],
|
||||
"tests/environment/docker-compose-minimal.yml": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/environment/docker-compose-minimal.yml",
|
||||
"hashed_secret": "9e3d103f7aa5f4f778cf752087dfceeba15d4fef",
|
||||
"is_verified": false,
|
||||
"line_number": 13
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/environment/docker-compose-minimal.yml",
|
||||
"hashed_secret": "92cda941553ea9ba9b39716c79a46b08a740613c",
|
||||
"is_verified": false,
|
||||
"line_number": 23
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/environment/docker-compose-minimal.yml",
|
||||
"hashed_secret": "d1ebbc78705ad98aa2c8fb357d2e30c8f001673a",
|
||||
"is_verified": false,
|
||||
"line_number": 79
|
||||
}
|
||||
],
|
||||
"tests/tas-cmis/src/test/resources/default.properties": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/tas-cmis/src/test/resources/default.properties",
|
||||
"hashed_secret": "d033e22ae348aeb5660fc2140aec35850c4da997",
|
||||
"is_verified": false,
|
||||
"line_number": 8
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/tas-cmis/src/test/resources/default.properties",
|
||||
"hashed_secret": "a4a747bd4ba5e3a5049cad116881867c71fb625b",
|
||||
"is_verified": false,
|
||||
"line_number": 84
|
||||
}
|
||||
],
|
||||
"tests/tas-email/src/test/resources/default.properties": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/tas-email/src/test/resources/default.properties",
|
||||
"hashed_secret": "d033e22ae348aeb5660fc2140aec35850c4da997",
|
||||
"is_verified": false,
|
||||
"line_number": 8
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/tas-email/src/test/resources/default.properties",
|
||||
"hashed_secret": "43b7ed74f99cf021109506832a48aa75435b817d",
|
||||
"is_verified": false,
|
||||
"line_number": 35
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/tas-email/src/test/resources/default.properties",
|
||||
"hashed_secret": "a4a747bd4ba5e3a5049cad116881867c71fb625b",
|
||||
"is_verified": false,
|
||||
"line_number": 97
|
||||
}
|
||||
],
|
||||
"tests/tas-integration/src/test/resources/default.properties": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/tas-integration/src/test/resources/default.properties",
|
||||
"hashed_secret": "d033e22ae348aeb5660fc2140aec35850c4da997",
|
||||
"is_verified": false,
|
||||
"line_number": 8
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/tas-integration/src/test/resources/default.properties",
|
||||
"hashed_secret": "a4a747bd4ba5e3a5049cad116881867c71fb625b",
|
||||
"is_verified": false,
|
||||
"line_number": 118
|
||||
}
|
||||
],
|
||||
"tests/tas-restapi/src/test/resources/default.properties": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/tas-restapi/src/test/resources/default.properties",
|
||||
"hashed_secret": "d033e22ae348aeb5660fc2140aec35850c4da997",
|
||||
"is_verified": false,
|
||||
"line_number": 21
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/tas-restapi/src/test/resources/default.properties",
|
||||
"hashed_secret": "a4a747bd4ba5e3a5049cad116881867c71fb625b",
|
||||
"is_verified": false,
|
||||
"line_number": 87
|
||||
}
|
||||
],
|
||||
"tests/tas-webdav/src/test/resources/default.properties": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/tas-webdav/src/test/resources/default.properties",
|
||||
"hashed_secret": "d033e22ae348aeb5660fc2140aec35850c4da997",
|
||||
"is_verified": false,
|
||||
"line_number": 8
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "tests/tas-webdav/src/test/resources/default.properties",
|
||||
"hashed_secret": "a4a747bd4ba5e3a5049cad116881867c71fb625b",
|
||||
"is_verified": false,
|
||||
"line_number": 73
|
||||
}
|
||||
]
|
||||
},
|
||||
"generated_at": "2025-03-18T15:20:48Z"
|
||||
}
|
||||
+5
-5
@@ -66,9 +66,9 @@ The repository code will need to talk to other ACS components, such as a databas
|
||||
The simplest way to create these, is to use the `docker-compose.yml` file in the `dev` directory.
|
||||
~~~
|
||||
# The `envUp` alias is the same as the following commands. Run these in a new terminal session, or add a `-d` flag to
|
||||
the `docker-compose` command.
|
||||
the `docker compose` command.
|
||||
cd acs-community-packaging
|
||||
docker-compose -f dev/docker-compose.yml up
|
||||
docker compose -f dev/docker-compose.yml up
|
||||
Creating dev_activemq_1 ... done
|
||||
Creating dev_solr6_1 ... done
|
||||
Creating dev_postgres_1 ... done
|
||||
@@ -120,13 +120,13 @@ mvn install -Prun,withShare -rf dev-acs-amps-overlay
|
||||
## Clean up
|
||||
When finished, kill the tomcat instance and stop the Docker instances. You will normally also
|
||||
remove the Docker containers, as you will need a clean database if you are going to issue
|
||||
another `mvn clean install` command. If you started `docker-compose` in a separate terminal session,
|
||||
simply use `^C` or if you used a `-d` flag, use `docker-compose -f dev/docker-compose.yml stop`.
|
||||
another `mvn clean install` command. If you started `docker compose` in a separate terminal session,
|
||||
simply use `^C` or if you used a `-d` flag, use `docker compose -f dev/docker-compose.yml stop`.
|
||||
~~~
|
||||
^C
|
||||
... Stopped 'sysAdmin' subsystem, ID: [sysAdmin, default]
|
||||
|
||||
docker-compose -f dev/docker-compose.yml rm
|
||||
docker compose -f dev/docker-compose.yml rm
|
||||
Going to remove dev_transform-core-aio_1, dev_transform-router_1, dev_solr6_1, dev_postgres_1, dev_activemq_1, dev_shared-file-store_1
|
||||
Are you sure? [yN] y
|
||||
Removing dev_transform-core-aio_1 ... done
|
||||
|
||||
+3
-3
@@ -65,6 +65,6 @@ alias comXS='comS && comP'
|
||||
alias drm='docker rm -f $(docker ps -q)'
|
||||
alias arm='rm -rf `find . -name alf_data`'
|
||||
alias envUp='./acs-community-packaging/scripts/dev/start-compose.sh'
|
||||
alias envStop='docker-compose -f acs-community-packaging/dev/docker-compose.yml stop'
|
||||
alias envKill='docker-compose -f acs-community-packaging/dev/docker-compose.yml kill'
|
||||
alias envRm='docker-compose -f acs-community-packaging/dev/docker-compose.yml rm'
|
||||
alias envStop='docker compose -f acs-community-packaging/dev/docker-compose.yml stop'
|
||||
alias envKill='docker compose -f acs-community-packaging/dev/docker-compose.yml kill'
|
||||
alias envRm='docker compose -f acs-community-packaging/dev/docker-compose.yml rm'
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -20,7 +20,7 @@ db.create.command=create database ${db.name}
|
||||
#db.name=alfresco
|
||||
#db.username=alfresco
|
||||
#db.password=alfresco
|
||||
#db.driver=com.mysql.jdbc.Driver
|
||||
#db.driver=com.mysql.cj.jdbc.Driver
|
||||
#db.url=jdbc:mysql://localhost/${db.name}?useUnicode=true&characterEncoding=UTF-8
|
||||
#db.master.url=jdbc:mysql://localhost/
|
||||
#db.master.username=${db.username}
|
||||
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
- 5432:5432
|
||||
|
||||
solr6:
|
||||
image: alfresco/alfresco-search-services:2.0.3
|
||||
image: alfresco/alfresco-search-services:2.0.21
|
||||
mem_limit: 2g
|
||||
environment:
|
||||
#Solr needs to know how to register itself with Alfresco
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>acs-community-packaging</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>acs-community-packaging</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>acs-community-packaging</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>content-services-community-docker</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>acs-community-packaging</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>share-community-docker</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>acs-community-packaging</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
</project>
|
||||
|
||||
@@ -3,19 +3,19 @@
|
||||
<artifactId>acs-community-packaging</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>Alfresco Content Services Community Packaging</name>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<relativePath>../alfresco-community-repo/pom.xml</relativePath>
|
||||
<version>23.2.0.34</version>
|
||||
<version>26.2.0.95</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<dependency.alfresco-community-repo.version>23.2.0.34</dependency.alfresco-community-repo.version>
|
||||
<dependency.alfresco-community-share.version>23.2.0.34</dependency.alfresco-community-share.version>
|
||||
<dependency.acs-packaging.version>23.2.0-A12</dependency.acs-packaging.version> <!-- for Share distribution zip -->
|
||||
<dependency.alfresco-community-repo.version>26.2.0.95</dependency.alfresco-community-repo.version>
|
||||
<dependency.alfresco-community-share.version>26.2.0.67</dependency.alfresco-community-share.version>
|
||||
<dependency.acs-packaging.version>26.2.0-A.30</dependency.acs-packaging.version> <!-- for Share distribution zip -->
|
||||
|
||||
<repo.image.tag>${dependency.alfresco-community-repo.version}</repo.image.tag>
|
||||
<share.image.tag>${dependency.alfresco-community-share.version}</share.image.tag>
|
||||
@@ -25,7 +25,7 @@
|
||||
<connection>scm:git:https://github.com/Alfresco/acs-community-packaging.git</connection>
|
||||
<developerConnection>scm:git:https://github.com/Alfresco/acs-community-packaging.git</developerConnection>
|
||||
<url>https://github.com/Alfresco/acs-community-packaging</url>
|
||||
<tag>23.2.0-A12</tag>
|
||||
<tag>HEAD</tag>
|
||||
</scm>
|
||||
|
||||
<distributionManagement>
|
||||
@@ -247,4 +247,13 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.diffplug.spotless</groupId>
|
||||
<artifactId>spotless-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>acs-community-packaging</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
|
||||
@@ -85,6 +85,16 @@ fi
|
||||
# Build the current project
|
||||
mvn -B -ntp -V -q install -DskipTests -Dmaven.javadoc.skip=true -P$BUILD_PROFILE -Pags ${REPO_IMAGE} ${SHARE_IMAGE}
|
||||
|
||||
# Build alfresco image with jdbc drivers (used by search API tests for MariaDB/MySQL)
|
||||
MYSQL_JDBC_TAG=$(mvn help:evaluate -Dexpression=dependency.mysql.version -q -DforceStdout)
|
||||
mvn dependency:copy -Dartifact=mysql:mysql-connector-java:${MYSQL_JDBC_TAG}:jar -DoutputDirectory=tests/environment/alfresco-with-jdbc-drivers
|
||||
|
||||
MARIADB_JDBC_TAG=$(mvn help:evaluate -Dexpression=dependency.mariadb.version -q -DforceStdout)
|
||||
mvn dependency:copy -Dartifact=org.mariadb.jdbc:mariadb-java-client:${MARIADB_JDBC_TAG}:jar -DoutputDirectory=tests/environment/alfresco-with-jdbc-drivers
|
||||
|
||||
REPO_LATEST_IMAGE=$(docker images --format='{{.Repository}}:{{.Tag}}' | grep "alfresco-content-repository-community:latest")
|
||||
docker build -t alfresco-repository-databases:latest -f tests/environment/alfresco-with-jdbc-drivers/alfresco.Dockerfile . --build-arg BASE_IMAGE=${REPO_LATEST_IMAGE}
|
||||
|
||||
|
||||
popd
|
||||
set +vex
|
||||
|
||||
@@ -2,4 +2,4 @@ set -x
|
||||
export TRANSFORMERS_TAG=$(mvn -f acs-community-packaging/pom.xml help:evaluate -Dexpression=dependency.alfresco-transform-core.version -q -DforceStdout)
|
||||
|
||||
# .env files are picked up from project directory correctly on docker-compose 1.23.0+
|
||||
docker-compose -f acs-community-packaging/dev/docker-compose.yml up
|
||||
docker compose -f acs-community-packaging/dev/docker-compose.yml up
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set +x
|
||||
|
||||
mvn spotless:apply validate -DlicenseUpdateHeaders=true -Pags,all-tas-tests > /dev/null || true
|
||||
|
||||
set -x
|
||||
@@ -1,3 +1,9 @@
|
||||
SOLR6_TAG=2.0.8.1
|
||||
POSTGRES_TAG=15.4
|
||||
ACTIVEMQ_TAG=5.18.3-jre17-rockylinux8
|
||||
SOLR6_TAG=2.0.21
|
||||
ACTIVEMQ_TAG=6.2.0-jre17-rockylinux8
|
||||
ELASTICSEARCH_TAG=8.17.0
|
||||
BATCH_INDEXING_TAG=5.7.0
|
||||
POSTGRES_TAG=17.9
|
||||
MYSQL_TAG=8.0.30
|
||||
MARIADB_TAG=11.8
|
||||
OPENSEARCH_TAG=2.11.1
|
||||
OPENSEARCH_DASHBOARDS_TAG=2.11.1
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#BUILDING CONTAINER FOR TAS TESTING WITH DIFFERENT JDBC CONNECTORS
|
||||
ARG BASE_IMAGE
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
COPY tests/environment/alfresco-with-jdbc-drivers/*.jar /usr/local/tomcat/lib/
|
||||
|
||||
+3
-1
@@ -6,11 +6,13 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>acs-community-packaging</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
<module>testcontainers-env</module>
|
||||
<module>tas-restapi</module>
|
||||
<module>tas-elasticsearch</module>
|
||||
<module>tas-cmis</module>
|
||||
<module>tas-email</module>
|
||||
<module>tas-webdav</module>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>content-repository-community-tests</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>content-repository-community-tests</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
@@ -21,7 +21,7 @@
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.24.2</version>
|
||||
<version>3.26.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
+128
-134
@@ -1,134 +1,128 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.distribution;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FilenameFilter;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.apache.maven.model.Model;
|
||||
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
|
||||
|
||||
public class CheckDistributionZipContents
|
||||
{
|
||||
public static final String ACS_PREFIX = "alfresco-content-services-community-distribution";
|
||||
public static final String ACS_DIR_NAME = "distribution";
|
||||
public static final String AGS_PREFIX = "alfresco-governance-services-community";
|
||||
public static final String AGS_DIR_NAME = "distribution-ags";
|
||||
public static final String FORMAT = ".zip";
|
||||
|
||||
@Test
|
||||
public void testAcsDistributionZipContents() throws Exception
|
||||
{
|
||||
File filesList[] = getDistributionZip(ACS_DIR_NAME, ACS_PREFIX);
|
||||
for (File file : filesList)
|
||||
{
|
||||
List<String> zipEntries = getZipEntries(file.getAbsolutePath());
|
||||
assertThat(zipEntries).
|
||||
contains(
|
||||
"keystore/metadata-keystore/keystore-passwords.properties",
|
||||
"keystore/metadata-keystore/keystore",
|
||||
"keystore/generate_keystores.bat",
|
||||
"keystore/generate_keystores.sh",
|
||||
"bin/alfresco-mmt.jar",
|
||||
"bin/apply_amps.bat",
|
||||
"bin/apply_amps.sh",
|
||||
"web-server/webapps/ROOT.war",
|
||||
"web-server/webapps/alfresco.war",
|
||||
"web-server/webapps/share.war",
|
||||
"web-server/webapps/_vti_bin.war",
|
||||
"web-server/conf/Catalina/localhost/alfresco.xml",
|
||||
"web-server/shared/classes/alfresco/web-extension/share-config-custom.xml"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAgsDistributionZipContents() throws Exception
|
||||
{
|
||||
String repoVersion = getPomValues().getProperties().getProperty("dependency.alfresco-community-repo.version");
|
||||
String shareVersion = getPomValues().getProperties().getProperty("dependency.alfresco-community-share.version");
|
||||
File filesList[] = getDistributionZip(AGS_DIR_NAME, AGS_PREFIX);
|
||||
for (File file : filesList)
|
||||
{
|
||||
List<String> zipEntries = getZipEntries(file.getAbsolutePath());
|
||||
assertThat(zipEntries).
|
||||
contains(
|
||||
"alfresco-governance-services-community-repo-" + repoVersion + ".amp",
|
||||
"alfresco-governance-services-community-rest-api-explorer-" + repoVersion + ".war",
|
||||
"alfresco-governance-services-community-share-" + shareVersion + ".amp"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private File[] getDistributionZip(String dirName, String prefix) throws Exception
|
||||
{
|
||||
String resourcePath = Paths.get("").toAbsolutePath().getParent().getParent().toString() + "/" + dirName + "/" + "target" + "/";
|
||||
File distributionZip = new File(resourcePath);
|
||||
FilenameFilter zipFileFilter = (dir, name) -> {
|
||||
if (name.startsWith(prefix) && name.endsWith(FORMAT))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return distributionZip.listFiles(zipFileFilter);
|
||||
}
|
||||
|
||||
private List<String> getZipEntries(String filePath) throws Exception
|
||||
{
|
||||
List<String> zipEntries = new ArrayList<>();
|
||||
ZipFile zipFile = new ZipFile(new File(filePath));
|
||||
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
||||
while (entries.hasMoreElements())
|
||||
{
|
||||
ZipEntry entry = entries.nextElement();
|
||||
zipEntries.add(entry.toString());
|
||||
}
|
||||
return zipEntries;
|
||||
}
|
||||
|
||||
private Model getPomValues() throws Exception
|
||||
{
|
||||
String parentPom = Paths.get("").toAbsolutePath().getParent().getParent().toString() + "/pom.xml";
|
||||
MavenXpp3Reader reader = new MavenXpp3Reader();
|
||||
Model model = reader.read(new FileReader(parentPom));
|
||||
return model;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2021 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.distribution;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FilenameFilter;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.apache.maven.model.Model;
|
||||
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class CheckDistributionZipContents
|
||||
{
|
||||
public static final String ACS_PREFIX = "alfresco-content-services-community-distribution";
|
||||
public static final String ACS_DIR_NAME = "distribution";
|
||||
public static final String AGS_PREFIX = "alfresco-governance-services-community";
|
||||
public static final String AGS_DIR_NAME = "distribution-ags";
|
||||
public static final String FORMAT = ".zip";
|
||||
|
||||
@Test
|
||||
public void testAcsDistributionZipContents() throws Exception
|
||||
{
|
||||
File filesList[] = getDistributionZip(ACS_DIR_NAME, ACS_PREFIX);
|
||||
for (File file : filesList)
|
||||
{
|
||||
List<String> zipEntries = getZipEntries(file.getAbsolutePath());
|
||||
assertThat(zipEntries).contains(
|
||||
"keystore/metadata-keystore/keystore-passwords.properties",
|
||||
"keystore/metadata-keystore/keystore",
|
||||
"bin/alfresco-mmt.jar",
|
||||
"bin/apply_amps.bat",
|
||||
"bin/apply_amps.sh",
|
||||
"web-server/webapps/ROOT.war",
|
||||
"web-server/webapps/alfresco.war",
|
||||
"web-server/webapps/share.war",
|
||||
"web-server/webapps/_vti_bin.war",
|
||||
"web-server/conf/Catalina/localhost/alfresco.xml",
|
||||
"web-server/shared/classes/alfresco/web-extension/share-config-custom.xml");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAgsDistributionZipContents() throws Exception
|
||||
{
|
||||
String repoVersion = getPomValues().getProperties().getProperty("dependency.alfresco-community-repo.version");
|
||||
String shareVersion = getPomValues().getProperties().getProperty("dependency.alfresco-community-share.version");
|
||||
File filesList[] = getDistributionZip(AGS_DIR_NAME, AGS_PREFIX);
|
||||
for (File file : filesList)
|
||||
{
|
||||
List<String> zipEntries = getZipEntries(file.getAbsolutePath());
|
||||
assertThat(zipEntries).contains(
|
||||
"alfresco-governance-services-community-repo-" + repoVersion + ".amp",
|
||||
"alfresco-governance-services-community-rest-api-explorer-" + repoVersion + ".war",
|
||||
"alfresco-governance-services-community-share-" + shareVersion + ".amp");
|
||||
}
|
||||
}
|
||||
|
||||
private File[] getDistributionZip(String dirName, String prefix) throws Exception
|
||||
{
|
||||
String resourcePath = Paths.get("").toAbsolutePath().getParent().getParent().toString() + "/" + dirName + "/" + "target" + "/";
|
||||
File distributionZip = new File(resourcePath);
|
||||
FilenameFilter zipFileFilter = (dir, name) -> {
|
||||
if (name.startsWith(prefix) && name.endsWith(FORMAT))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return distributionZip.listFiles(zipFileFilter);
|
||||
}
|
||||
|
||||
private List<String> getZipEntries(String filePath) throws Exception
|
||||
{
|
||||
List<String> zipEntries = new ArrayList<>();
|
||||
ZipFile zipFile = new ZipFile(new File(filePath));
|
||||
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
||||
while (entries.hasMoreElements())
|
||||
{
|
||||
ZipEntry entry = entries.nextElement();
|
||||
zipEntries.add(entry.toString());
|
||||
}
|
||||
return zipEntries;
|
||||
}
|
||||
|
||||
private Model getPomValues() throws Exception
|
||||
{
|
||||
String parentPom = Paths.get("").toAbsolutePath().getParent().getParent().toString() + "/pom.xml";
|
||||
MavenXpp3Reader reader = new MavenXpp3Reader();
|
||||
Model model = reader.read(new FileReader(parentPom));
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
<?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>
|
||||
<groupId>org.alfresco.tas</groupId>
|
||||
<artifactId>content-repository-community-elasticsearch-test</artifactId>
|
||||
<name>Elasticsearch test</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>content-repository-community-tests</artifactId>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>elastic</id>
|
||||
<properties>
|
||||
<suiteXmlFile>${project.basedir}/src/test/resources/test-suites/standard-elasticsearch-suites.xml</suiteXmlFile>
|
||||
<search.engine.type />
|
||||
<database.type />
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>elastic-basic-auth</id>
|
||||
<properties>
|
||||
<suiteXmlFile>${project.basedir}/src/test/resources/test-suites/elasticsearch-basic-auth-suite.xml</suiteXmlFile>
|
||||
<search.engine.type />
|
||||
<database.type />
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.alfresco.tas</groupId>
|
||||
<artifactId>content-repository-community-testcontainers</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.alfresco.tas</groupId>
|
||||
<artifactId>restapi</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.alfresco.tas</groupId>
|
||||
<artifactId>restapi</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.rest-assured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.parsson</groupId>
|
||||
<artifactId>parsson</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.opensearch.client</groupId>
|
||||
<artifactId>opensearch-java</artifactId>
|
||||
<version>${dependency.opensearch.version}</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse</groupId>
|
||||
<artifactId>yasson</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>false</redirectTestOutputToFile>
|
||||
<suiteXmlFiles>
|
||||
<suiteXmlFile>${suiteXmlFile}</suiteXmlFile>
|
||||
</suiteXmlFiles>
|
||||
<!-- Keeping illegal-access=warn for Java 11 compatibility, even though it has no effect on JDK 21 -->
|
||||
<argLine>
|
||||
--illegal-access=warn
|
||||
--add-opens=java.base/java.lang=ALL-UNNAMED
|
||||
</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Build only test jar -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>test-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
import static org.alfresco.utility.model.FileType.TEXT_PLAIN;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.elasticsearch.utility.ElasticsearchRESTHelper;
|
||||
import org.alfresco.rest.core.RestWrapper;
|
||||
import org.alfresco.rest.model.RestCategoryLinkBodyModel;
|
||||
import org.alfresco.rest.model.RestCategoryModel;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.data.RandomData;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
import org.alfresco.utility.report.log.Step;
|
||||
|
||||
/**
|
||||
* Tests to verify batch indexing of category using Elasticsearch.
|
||||
*/
|
||||
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"})
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
public class CategoryIndexingTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
private static final String TEST_PREFIX = RandomData.getRandomAlphanumeric() + "_";
|
||||
private static final String CATEGORY_A_NAME = TEST_PREFIX + "CategoryA";
|
||||
private static final String CATEGORY_B_NAME = TEST_PREFIX + "CategoryB";
|
||||
private static final String ROOT_CATEGORY_ID = "-root-";
|
||||
private static final RestCategoryModel ROOT_CATEGORY = RestCategoryModel.builder().id(ROOT_CATEGORY_ID).create();
|
||||
|
||||
@Autowired
|
||||
private ElasticsearchRESTHelper helper;
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
@Autowired
|
||||
private DataSite dataSite;
|
||||
@Autowired
|
||||
private DataContent dataContent;
|
||||
@Autowired
|
||||
protected SearchQueryService searchQueryService;
|
||||
@Autowired
|
||||
protected RestWrapper restClient;
|
||||
|
||||
private RestCategoryModel categoryA;
|
||||
private RestCategoryModel categoryB;
|
||||
private UserModel testUser;
|
||||
private SiteModel testSite;
|
||||
private FileModel testFile;
|
||||
private FolderModel testFolder;
|
||||
|
||||
/** Create a user, private site and two categories. Create a folder (in category B) containing a document (in category A and category B). */
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
serverHealth.isServerReachable();
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
Step.STEP("Create two categories under the root.");
|
||||
List<RestCategoryModel> categories = List.of(RestCategoryModel.builder().name(CATEGORY_A_NAME).create(),
|
||||
RestCategoryModel.builder().name(CATEGORY_B_NAME).create());
|
||||
List<RestCategoryModel> categoryList = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
|
||||
.usingCategory(ROOT_CATEGORY).createCategoriesList(categories).getEntries();
|
||||
categoryA = categoryList.get(0).onModel();
|
||||
categoryB = categoryList.get(1).onModel();
|
||||
|
||||
Step.STEP("Create a test user and use them to create a private site with a folder containing a document.");
|
||||
testUser = dataUser.createRandomTestUser();
|
||||
testSite = dataSite.usingUser(testUser).createPrivateRandomSite();
|
||||
FolderModel folderModel = FolderModel.getRandomFolderModel();
|
||||
testFolder = dataContent.usingUser(testUser).usingSite(testSite).createFolder(folderModel);
|
||||
FileModel fileModel = FileModel.getRandomFileModel(TEXT_PLAIN);
|
||||
testFile = dataContent.usingUser(testUser).usingResource(testFolder).createContent(fileModel);
|
||||
|
||||
Step.STEP("Assign the document to both categories and the folder to category B.");
|
||||
RestCategoryLinkBodyModel categoryALink = RestCategoryLinkBodyModel.builder().categoryId(categoryA.getId()).create();
|
||||
RestCategoryLinkBodyModel categoryBLink = RestCategoryLinkBodyModel.builder().categoryId(categoryB.getId()).create();
|
||||
restClient.authenticateUser(testUser).withCoreAPI().usingNode(testFile).linkToCategory(categoryALink);
|
||||
restClient.authenticateUser(testUser).withCoreAPI().usingNode(testFile).linkToCategory(categoryBLink);
|
||||
restClient.authenticateUser(testUser).withCoreAPI().usingNode(testFolder).linkToCategory(categoryBLink);
|
||||
}
|
||||
|
||||
/** Check we can find the document assigned to a category. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testFindDocumentByCategory()
|
||||
{
|
||||
SearchRequest query = req("cm:categories:\"" + categoryA.getId() + "\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
|
||||
}
|
||||
|
||||
/** Check we can find the document assigned to a category even when the query includes a StoreRef. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testFindDocumentByCategoryWithStoreRef()
|
||||
{
|
||||
SearchRequest query = req("cm:categories:\"workspace://SpacesStore/" + categoryA.getId() + "\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
|
||||
}
|
||||
|
||||
/** Check we can find the folder and document assigned to the other category. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testFindFolderByCategory()
|
||||
{
|
||||
SearchRequest query = req("cm:categories:\"" + categoryB.getId() + "\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName(), testFolder.getName());
|
||||
}
|
||||
|
||||
/** Check we can find the document by the pseudo-path created for the category. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testQueryByCategoryPseudoPath()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"/cm:categoryRoot/cm:generalclassifiable/cm:" + CATEGORY_A_NAME + "/*\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
|
||||
}
|
||||
|
||||
/** Check we can find the document by a partial path match for the category. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testQueryByPartialCategoryPathA()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"//cm:" + CATEGORY_A_NAME + "/*\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
|
||||
}
|
||||
|
||||
/** Check we can find the document and folder by a partial path match for the second category. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testQueryByPartialCategoryPathB()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"//cm:" + CATEGORY_B_NAME + "/*\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName(), testFolder.getName());
|
||||
}
|
||||
|
||||
/** Check we cannot find the document by a partial path match for the category that has been applied to it and then deleted. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testQueryByPathOnDeletedCategory() throws InterruptedException
|
||||
{
|
||||
// create 2 categories
|
||||
final RestCategoryModel categoryToDelete = helper.createCategory();
|
||||
final RestCategoryModel otherCategory = helper.createCategory();
|
||||
|
||||
// assign both categories to a document
|
||||
helper.linkToCategory(testUser, testFile, categoryToDelete);
|
||||
helper.linkToCategory(testUser, testFile, otherCategory);
|
||||
|
||||
// we can find the document by partial category paths after it is linked to both categories
|
||||
SearchRequest categoryToDeleteQuery = req("PATH:\"//cm:" + categoryToDelete.getName() + "/*\"");
|
||||
searchQueryService.expectResultsFromQuery(categoryToDeleteQuery, testUser, testFile.getName());
|
||||
SearchRequest otherCategoryQuery = req("PATH:\"//cm:" + otherCategory.getName() + "/*\"");
|
||||
searchQueryService.expectResultsFromQuery(otherCategoryQuery, testUser, testFile.getName());
|
||||
|
||||
// delete one of linked categories
|
||||
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
|
||||
.usingCategory(categoryToDelete).deleteCategory();
|
||||
restClient.assertStatusCodeIs(HttpStatus.NO_CONTENT);
|
||||
|
||||
// we cannot find the document by partial category path anymore after the category is deleted
|
||||
searchQueryService.expectNoResultsFromQuery(categoryToDeleteQuery, testUser);
|
||||
// we can still find the document by partial category path with a different category that has been assigned but not deleted
|
||||
searchQueryService.expectResultsFromQuery(otherCategoryQuery, testUser, testFile.getName());
|
||||
}
|
||||
}
|
||||
+395
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.EMPTY;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
import static org.alfresco.utility.data.RandomData.getRandomFile;
|
||||
import static org.alfresco.utility.data.RandomData.getRandomName;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.model.RestTagModel;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.ContentModel;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
public class ElasticsearchBoostedSearchTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
private static final String SEARCH_TERM = "mountain";
|
||||
private static final String DIFFERENT_SEARCH_TERM = "fountain";
|
||||
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
|
||||
@Autowired
|
||||
private DataContent dataContent;
|
||||
|
||||
@Autowired
|
||||
private SearchQueryService searchQueryService;
|
||||
|
||||
private UserModel testUser;
|
||||
private ContentModel fileWithTermInName;
|
||||
private ContentModel fileWithDifferentTermInName;
|
||||
private ContentModel fileWithPhraseInContent;
|
||||
private ContentModel fileWithTermInTitle;
|
||||
private ContentModel folderWithTermInName;
|
||||
private ContentModel folderWithTermInTitle;
|
||||
private ContentModel testFolder;
|
||||
private LocalDateTime creationTime;
|
||||
private LocalDateTime afterCreationTime;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
STEP("Create a test user and few files and folders containing searched term in name, title and content");
|
||||
testUser = dataUser.createRandomTestUser();
|
||||
testFolder = createFolder(getRandomName("folder"));
|
||||
fileWithTermInName = createFile(testFolder, SEARCH_TERM + ".txt", "dummy content");
|
||||
fileWithDifferentTermInName = createFile(testFolder, DIFFERENT_SEARCH_TERM + ".txt", "dummy other content");
|
||||
fileWithPhraseInContent = createFile(testFolder, getRandomFile(FileType.TEXT_PLAIN), "content with " + SEARCH_TERM + " searched phrase");
|
||||
folderWithTermInName = createFolder(testFolder, SEARCH_TERM);
|
||||
creationTime = ZonedDateTime.now(Clock.system(ZoneOffset.UTC)).toLocalDateTime();
|
||||
fileWithTermInTitle = createRandomFileWithTitle(testFolder, SEARCH_TERM);
|
||||
folderWithTermInTitle = createRandomFolderWithTitle(testFolder, SEARCH_TERM);
|
||||
afterCreationTime = ZonedDateTime.now(Clock.system(ZoneOffset.UTC)).toLocalDateTime();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public void dataCleanup()
|
||||
{
|
||||
STEP("Clean up created files, folders and user");
|
||||
dataContent.usingAdmin().usingResource(testFolder).deleteContent();
|
||||
dataUser.deleteUser(testUser);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_simpleTermBoost()
|
||||
{
|
||||
STEP("Search for files and folders by name with higher priority for files");
|
||||
String boostedQuery = "TYPE:('cm:content'^2 OR 'cm:folder'^0.5) AND cm:name:" + SEARCH_TERM;
|
||||
SearchRequest searchRequest = req("afts", boostedQuery);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), folderWithTermInName.getName());
|
||||
|
||||
STEP("Search for files and folders by name with higher priority for folders");
|
||||
String invertedBoost = "TYPE:('cm:content'^0.5 OR 'cm:folder'^2) AND cm:name:" + SEARCH_TERM;
|
||||
searchRequest = req("afts", invertedBoost);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, folderWithTermInName.getName(), fileWithTermInName.getName());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_complexTermBoost()
|
||||
{
|
||||
STEP("Search for files and folders by name or title with higher priority for files by name");
|
||||
String boostedQuery1 = "TYPE:('cm:content'^4 OR 'cm:folder'^0.5)^6 AND (cm:name:" + SEARCH_TERM + "^1.5 OR cm:title:" + SEARCH_TERM + "^0.5)";
|
||||
SearchRequest searchRequest = req("afts", boostedQuery1);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), fileWithTermInTitle.getName(), folderWithTermInName.getName(), folderWithTermInTitle.getName());
|
||||
|
||||
STEP("Search for files and folders by name or title with higher priority for folders by name");
|
||||
String boostedQuery2 = "TYPE:('cm:content'^0.5 OR 'cm:folder'^4)^6 AND (cm:name:" + SEARCH_TERM + "^1.5 OR cm:title:" + SEARCH_TERM + "^0.5)";
|
||||
searchRequest = req("afts", boostedQuery2);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, folderWithTermInName.getName(), folderWithTermInTitle.getName(), fileWithTermInName.getName(), fileWithTermInTitle.getName());
|
||||
|
||||
STEP("Search for files and folders by name or title with higher priority for files by title");
|
||||
String boostedQuery3 = "TYPE:('cm:content'^4 OR 'cm:folder'^0.5)^6 AND (cm:name:" + SEARCH_TERM + "^0.5 OR cm:title:" + SEARCH_TERM + "^1.5)";
|
||||
searchRequest = req("afts", boostedQuery3);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInTitle.getName(), fileWithTermInName.getName(), folderWithTermInTitle.getName(), folderWithTermInName.getName());
|
||||
|
||||
STEP("Search for files and folders by name or title with higher priority for folders by title");
|
||||
String boostedQuery4 = "TYPE:('cm:content'^0.5 OR 'cm:folder'^4)^6 AND (cm:name:" + SEARCH_TERM + "^0.5 OR cm:title:" + SEARCH_TERM + "^1.5)";
|
||||
searchRequest = req("afts", boostedQuery4);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, folderWithTermInTitle.getName(), folderWithTermInName.getName(), fileWithTermInTitle.getName(), fileWithTermInName.getName());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_phraseBoost()
|
||||
{
|
||||
STEP("Search for files by name or TEXT with higher priority for name filter");
|
||||
String boostedQuery = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "^2 OR TEXT:'" + SEARCH_TERM + " searched'^0.1)";
|
||||
SearchRequest searchRequest = req("afts", boostedQuery);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), fileWithPhraseInContent.getName());
|
||||
|
||||
STEP("Search for files by name or TEXT with higher priority for TEXT filter");
|
||||
String invertedBoost = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "^0.1 OR TEXT:'" + SEARCH_TERM + " searched'^2)";
|
||||
searchRequest = req("afts", invertedBoost);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithPhraseInContent.getName(), fileWithTermInName.getName());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_exactTermBoost()
|
||||
{
|
||||
STEP("Search for files by exact name or content with higher priority for exact name filter");
|
||||
String boostedQuery = "TYPE:'cm:content' AND (=cm:name:" + SEARCH_TERM + ".txt^2 OR =cm:content:" + SEARCH_TERM + "^0.5)";
|
||||
SearchRequest searchRequest = req("afts", boostedQuery);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), fileWithPhraseInContent.getName());
|
||||
|
||||
STEP("Search for files by exact name or content with higher priority for exact content filter");
|
||||
String invertedBoost = "TYPE:'cm:content' AND (=cm:name:" + SEARCH_TERM + ".txt^0.1 OR =cm:content:" + SEARCH_TERM + "^3)";
|
||||
searchRequest = req("afts", invertedBoost);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithPhraseInContent.getName(), fileWithTermInName.getName());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_expandedTermBoost()
|
||||
{
|
||||
STEP("Search for files by expanded name and two different terms with higher priority for first term");
|
||||
String boostedQuery = "TYPE:'cm:content' AND (~cm:name:" + SEARCH_TERM + "^3 OR ~cm:name:" + DIFFERENT_SEARCH_TERM + "^0.1)";
|
||||
SearchRequest searchRequest = req("afts", boostedQuery);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), fileWithDifferentTermInName.getName());
|
||||
|
||||
STEP("Search for files by expanded name and two different terms with higher priority for second term");
|
||||
String invertedBoost = "TYPE:'cm:content' AND (~cm:name:" + SEARCH_TERM + "^0.1 OR ~cm:name:" + DIFFERENT_SEARCH_TERM + "^3)";
|
||||
searchRequest = req("afts", invertedBoost);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithDifferentTermInName.getName(), fileWithTermInName.getName());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_fuzzyMatchingBoost()
|
||||
{
|
||||
STEP("Fuzzy matching search for files by name or title with higher priority for fuzzy name filter");
|
||||
String boostedQuery = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "~0.7^3 OR cm:title:" + SEARCH_TERM + "^0.01)";
|
||||
SearchRequest searchRequest = req("afts", boostedQuery);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), fileWithDifferentTermInName.getName(), fileWithTermInTitle.getName());
|
||||
|
||||
STEP("Fuzzy matching search for files by name or title with higher priority for fuzzy title filter");
|
||||
String invertedBoost = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "~0.7^0.01 OR cm:title:" + SEARCH_TERM + "^3)";
|
||||
searchRequest = req("afts", invertedBoost);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInTitle.getName(), fileWithTermInName.getName(), fileWithDifferentTermInName.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if boosts works fine with words proximity search. Files containing terms within specific distance from another one should be returned.
|
||||
*/
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_proximitySearchBoost()
|
||||
{
|
||||
STEP("Search for files by name or proximity TEXT with higher priority for name filter");
|
||||
String boostedQuery = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "^5 OR TEXT:(" + SEARCH_TERM + " *(1) phrase)^0.1)";
|
||||
SearchRequest searchRequest = req("afts", boostedQuery);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), fileWithPhraseInContent.getName());
|
||||
|
||||
STEP("Search for files by name or proximity TEXT with higher priority for proximity TEXT filter");
|
||||
String invertedBoost = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "^0.1 OR TEXT:(" + SEARCH_TERM + " *(1) phrase)^5)";
|
||||
searchRequest = req("afts", invertedBoost);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithPhraseInContent.getName(), fileWithTermInName.getName());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_dateRangeSearchBoost()
|
||||
{
|
||||
String timeFrom = creationTime.format(DateTimeFormatter.ISO_DATE_TIME);
|
||||
String timeTo = afterCreationTime.format(DateTimeFormatter.ISO_DATE_TIME);
|
||||
|
||||
STEP("Search for files and folders by name or creation time range with higher priority for name filter");
|
||||
String boostedQuery = "TYPE:('cm:content'^16 OR 'cm:folder'^1) AND (cm:name:" + SEARCH_TERM + "^4 OR cm:created:['" + timeFrom + "' TO '" + timeTo + "']^0.1)^3";
|
||||
SearchRequest searchRequest = req("afts", boostedQuery);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, fileWithTermInName.getName(), folderWithTermInName.getName(), fileWithTermInTitle.getName(), folderWithTermInTitle.getName());
|
||||
|
||||
STEP("Search for files and folders by name or creation time range with higher priority for creation time range filter");
|
||||
String invertedBoost = "TYPE:('cm:content' OR 'cm:folder'^3) AND (cm:name:" + SEARCH_TERM + "^0.1 OR cm:created:['" + timeFrom + "' TO '" + timeTo + "']^4)^3";
|
||||
searchRequest = req("afts", invertedBoost);
|
||||
searchQueryService.expectResultsInOrder(searchRequest, testUser, folderWithTermInTitle.getName(), fileWithTermInTitle.getName(), folderWithTermInName.getName(), fileWithTermInName.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if boosts works fine with words range search. Files containing words from alphabetical range (from 'mountain' to 'phrase', this includes word 'other') should be returned.
|
||||
*/
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_wordsRangeSearchBoost()
|
||||
{
|
||||
String contentPath = "AND PATH:\"/app:company_home/cm:" + testFolder.getName() + "//*\"";
|
||||
|
||||
STEP("Search for files by name or words in content from given range with higher priority for name filter");
|
||||
String boostedQuery = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "^3 OR cm:content:" + SEARCH_TERM + "..phrase^0.1) " + contentPath;
|
||||
SearchRequest searchRequest = req("afts", boostedQuery);
|
||||
searchQueryService.expectResultsStartingWithOneOf(searchRequest, testUser, fileWithTermInName.getName());
|
||||
searchQueryService.expectResultsFromQuery(searchRequest, testUser, fileWithTermInName.getName(), fileWithPhraseInContent.getName(), fileWithDifferentTermInName.getName());
|
||||
|
||||
STEP("Search for files by name or words in content from given range with higher priority for words in content from given range filter");
|
||||
String invertedBoost = "TYPE:'cm:content' AND (cm:name:" + SEARCH_TERM + "^0.1 OR cm:content:" + SEARCH_TERM + "..phrase^3) " + contentPath;
|
||||
searchRequest = req("afts", invertedBoost);
|
||||
searchQueryService.expectResultsStartingWithOneOf(searchRequest, testUser, fileWithPhraseInContent.getName(), fileWithDifferentTermInName.getName());
|
||||
searchQueryService.expectResultsFromQuery(searchRequest, testUser, fileWithTermInName.getName(), fileWithPhraseInContent.getName(), fileWithDifferentTermInName.getName());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_wildcardSearchBoost()
|
||||
{
|
||||
STEP("Search for files by wildcard name or title with higher priority for wildcard name filter");
|
||||
String wildcardTerm = SEARCH_TERM.replaceFirst("^.", "?");
|
||||
String boostedQuery = "TYPE:'cm:content' AND (cm:name:" + wildcardTerm + "^3 OR cm:title:" + SEARCH_TERM + "^0.1)";
|
||||
SearchRequest searchRequest = req("afts", boostedQuery);
|
||||
searchQueryService.expectResultsStartingWithOneOf(searchRequest, testUser, fileWithTermInName.getName(), fileWithDifferentTermInName.getName());
|
||||
searchQueryService.expectResultsFromQuery(searchRequest, testUser, fileWithTermInName.getName(), fileWithDifferentTermInName.getName(), fileWithTermInTitle.getName());
|
||||
|
||||
STEP("Search for files by wildcard name or title with higher priority for wildcard title filter");
|
||||
wildcardTerm = SEARCH_TERM.replaceFirst("^.", "*");
|
||||
String invertedBoost = "TYPE:'cm:content' AND (cm:name:" + wildcardTerm + "^0.1 OR cm:title:" + SEARCH_TERM + "^3)";
|
||||
searchRequest = req("afts", invertedBoost);
|
||||
searchQueryService.expectResultsStartingWithOneOf(searchRequest, testUser, fileWithTermInTitle.getName());
|
||||
searchQueryService.expectResultsFromQuery(searchRequest, testUser, fileWithTermInName.getName(), fileWithDifferentTermInName.getName(), fileWithTermInTitle.getName());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_invalidNegativeBoost()
|
||||
{
|
||||
STEP("Try to search for files by name using negative boost and expect 500 error response");
|
||||
String boostedQuery = "TYPE:'cm:content'^-2 AND cm:name:" + SEARCH_TERM;
|
||||
SearchRequest searchRequest = req("afts", boostedQuery);
|
||||
searchQueryService.expectErrorFromQuery(searchRequest, testUser, HttpStatus.INTERNAL_SERVER_ERROR, EMPTY);
|
||||
}
|
||||
|
||||
private ContentModel createRandomFileWithTitle(ContentModel parent, String title)
|
||||
{
|
||||
return createRandomFile(parent, title, null, null);
|
||||
}
|
||||
|
||||
private ContentModel createRandomFileWithTitle(String title)
|
||||
{
|
||||
return createRandomFile(title, null, null);
|
||||
}
|
||||
|
||||
private ContentModel createRandomFile(ContentModel parent, String title, String description, String tag)
|
||||
{
|
||||
return createFile(parent, getRandomFile(FileType.TEXT_PLAIN), "dummy content", title, description, tag);
|
||||
}
|
||||
|
||||
private ContentModel createRandomFile(String title, String description, String tag)
|
||||
{
|
||||
return createFile(getRandomFile(FileType.TEXT_PLAIN), "dummy content", title, description, tag);
|
||||
}
|
||||
|
||||
private ContentModel createFile(ContentModel parent, String filename, String content)
|
||||
{
|
||||
return createFile(parent, filename, content, null, null, null);
|
||||
}
|
||||
|
||||
private ContentModel createFile(String filename, String content)
|
||||
{
|
||||
return createFile(filename, content, null, null, null);
|
||||
}
|
||||
|
||||
private ContentModel createFile(ContentModel parent, String filename, String content, String title, String description, String tag)
|
||||
{
|
||||
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
|
||||
fileModel.setTitle(title);
|
||||
fileModel.setDescription(description);
|
||||
|
||||
FileModel file = dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(parent)
|
||||
.createContent(fileModel);
|
||||
|
||||
if (StringUtils.isNotBlank(tag))
|
||||
{
|
||||
dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(file)
|
||||
.addTagToContent(RestTagModel.builder().tag(tag).create());
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private ContentModel createFile(String filename, String content, String title, String description, String tag)
|
||||
{
|
||||
ContentModel contentRoot = new ContentModel("-root-");
|
||||
contentRoot.setNodeRef(contentRoot.getName());
|
||||
return createFile(contentRoot, filename, content, title, description, tag);
|
||||
}
|
||||
|
||||
private ContentModel createRandomFolderWithTitle(ContentModel parent, String title)
|
||||
{
|
||||
return createFolder(parent, getRandomName("folder"), title, null, null);
|
||||
}
|
||||
|
||||
private ContentModel createFolder(ContentModel parent, String folderName)
|
||||
{
|
||||
return createFolder(parent, folderName, null, null, null);
|
||||
}
|
||||
|
||||
private ContentModel createFolder(String folderName)
|
||||
{
|
||||
return createFolder(folderName, null, null, null);
|
||||
}
|
||||
|
||||
private ContentModel createFolder(ContentModel parent, String folderName, String title, String description, String tag)
|
||||
{
|
||||
FolderModel folderModel = new FolderModel(folderName, title, description);
|
||||
|
||||
FolderModel folder = dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(parent)
|
||||
.createFolder(folderModel);
|
||||
|
||||
if (StringUtils.isNotBlank(tag))
|
||||
{
|
||||
dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(folder)
|
||||
.addTagToContent(RestTagModel.builder().tag(tag).create());
|
||||
}
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
private ContentModel createFolder(String folderName, String title, String description, String tag)
|
||||
{
|
||||
ContentModel contentRoot = new ContentModel("-root-");
|
||||
contentRoot.setNodeRef(contentRoot.getName());
|
||||
return createFolder(contentRoot, folderName, title, description, tag);
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
import static org.alfresco.tas.TestDataUtility.getAlphabeticUUID;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
/**
|
||||
* Tests for CMIS queries that require path indexing against Elasticsearch.
|
||||
*/
|
||||
public class ElasticsearchCMISPathTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
private static final String PREFIX = getAlphabeticUUID();
|
||||
private static final String FOLDER_0_NAME = PREFIX + "_folder0";
|
||||
private static final String FOLDER_00_NAME = PREFIX + "_folder00";
|
||||
private static final String FOLDER_000_NAME = PREFIX + "_folder000";
|
||||
private static final String FOLDER_1_NAME = PREFIX + "_folder1";
|
||||
private static final String DOC_0000_NAME = PREFIX + "_doc0000.txt";
|
||||
private static final String DOC_00_NAME = PREFIX + "_doc00.txt";
|
||||
private static final String DOC_01_NAME = PREFIX + "_doc01.txt";
|
||||
private static final String DOC_10_NAME = PREFIX + "_doc10.txt";
|
||||
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
@Autowired
|
||||
private DataContent dataContent;
|
||||
@Autowired
|
||||
private DataSite dataSite;
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
@Autowired
|
||||
protected SearchQueryService searchQueryService;
|
||||
|
||||
private UserModel user;
|
||||
private SiteModel siteModel;
|
||||
private FolderModel folder0;
|
||||
private FolderModel folder00;
|
||||
private FolderModel folder000;
|
||||
private FolderModel folder1;
|
||||
private FileModel document0000;
|
||||
private FileModel document00;
|
||||
private FileModel document01;
|
||||
private FileModel document10;
|
||||
|
||||
/**
|
||||
* Data will be prepared using the schema below:
|
||||
*
|
||||
* <pre>
|
||||
* Site
|
||||
* + Document Library
|
||||
* +-folder0
|
||||
* | +-folder00
|
||||
* | | +-folder000
|
||||
* | | +-document0000
|
||||
* | +-document00
|
||||
* | +-document01
|
||||
* +-folder1
|
||||
* +-document10
|
||||
* </pre>
|
||||
*/
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
user = dataUser.createRandomTestUser();
|
||||
|
||||
siteModel = dataSite.usingUser(user).createPrivateRandomSite();
|
||||
|
||||
folder0 = dataContent.usingUser(user).usingSite(siteModel).createFolderCmisApi(FOLDER_0_NAME);
|
||||
folder1 = dataContent.usingUser(user).usingSite(siteModel).createFolderCmisApi(FOLDER_1_NAME);
|
||||
|
||||
folder00 = dataContent.usingUser(user).usingResource(folder0).createFolderCmisApi(FOLDER_00_NAME);
|
||||
folder000 = dataContent.usingUser(user).usingResource(folder00).createFolderCmisApi(FOLDER_000_NAME);
|
||||
|
||||
document0000 = createContent(DOC_0000_NAME, "This is document 0000", folder000, user);
|
||||
document00 = createContent(DOC_00_NAME, "This is document 00", folder0, user);
|
||||
document01 = createContent(DOC_01_NAME, "This is document 01", folder0, user);
|
||||
document10 = createContent(DOC_10_NAME, "This is document 10", folder1, user);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void inTreeQuery_selectSubfolders()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder WHERE IN_TREE('" + folder0.getNodeRef() + "')");
|
||||
searchQueryService.expectResultsFromQuery(query, user, FOLDER_00_NAME, FOLDER_000_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void inTreeQuery_selectDocuments()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE IN_TREE('" + folder0.getNodeRef() + "')");
|
||||
searchQueryService.expectResultsFromQuery(query, user, DOC_0000_NAME, DOC_00_NAME, DOC_01_NAME);
|
||||
}
|
||||
|
||||
private FileModel createContent(String filename, String content, FolderModel folderModel, UserModel user)
|
||||
{
|
||||
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
|
||||
return dataContent.usingUser(user).usingResource(folderModel).createContent(fileModel);
|
||||
}
|
||||
}
|
||||
+604
@@ -0,0 +1,604 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
import static org.alfresco.tas.TestDataUtility.getAlphabeticUUID;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.Utility;
|
||||
import org.alfresco.utility.constants.UserRole;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
/**
|
||||
* In this test we are verifying end-to-end the indexing and CMIS queries against Elasticsearch. In order to test ACLs we created 2 sites and 3 users.
|
||||
*/
|
||||
public class ElasticsearchCMISTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
private static final String PREFIX = getAlphabeticUUID();
|
||||
private static final String SUFFIX = getAlphabeticUUID();
|
||||
private static final String UNIQUE_WORD = getAlphabeticUUID();
|
||||
private static final String FILE_0_NAME = PREFIX + "_test.txt" + SUFFIX;
|
||||
private static final String FILE_1_NAME = "internal_" + PREFIX + "_and_" + SUFFIX + ".txt";
|
||||
private static final String FILE_2_NAME = PREFIX + "_user2doc_" + SUFFIX;
|
||||
/** This is a file that user 1 doesn't have access to and so shouldn't be returned in their search results. */
|
||||
private static final String USER_2_FILE_NAME = PREFIX + "_user2only_" + SUFFIX;
|
||||
private static final String FOLDER_PREFIX = getAlphabeticUUID();
|
||||
private static final String FOLDER_0_NAME = FOLDER_PREFIX + "_folder0";
|
||||
private static final String FOLDER_1_NAME = FOLDER_PREFIX + "_folder1";
|
||||
private static final int X_DIMENSION = 48;
|
||||
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
|
||||
@Autowired
|
||||
private DataContent dataContent;
|
||||
|
||||
@Autowired
|
||||
private DataSite dataSite;
|
||||
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
|
||||
@Autowired
|
||||
protected SearchQueryService searchQueryService;
|
||||
|
||||
private UserModel user1;
|
||||
private UserModel user2;
|
||||
private UserModel userMultiSite;
|
||||
private SiteModel siteModel1;
|
||||
private SiteModel siteModel2;
|
||||
private FileModel file0;
|
||||
private List<String> fileCreationDates;
|
||||
private String file3Name;
|
||||
/**
|
||||
* Scoping predicate restricting the result universe to the 4 documents user1 owns in this class. Used to AND into negative / open-range queries so they aren't polluted by bootstrap content or by files created in other test classes that share the same Spring context.
|
||||
*/
|
||||
private String user1FilesScope;
|
||||
/**
|
||||
* Scoping predicate restricting to file3 (the only EXIF-bearing image we upload). Used to AND into negative / open-range integer queries on exif:pixelXDimension.
|
||||
*/
|
||||
private String file3Scope;
|
||||
|
||||
/**
|
||||
* Data will be prepared using the schema below:
|
||||
* <p>
|
||||
* Site1: - Users: user1, userMultiSite - Documents: FILE_0_NAME (owner: user1), FILE_1_NAME (owner: user1), FILE_2_NAME (owner: user2), file3Name (owner: user1) - Folders: FOLDER_0_NAME (owner: user1), FOLDER_1_NAME (owner: user1)
|
||||
* <p>
|
||||
* Site2: - Users: user2, userMultiSite - Documents: USER_2_FILE_NAME (owner: user2)
|
||||
*/
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation() throws IOException, InterruptedException
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
user1 = dataUser.createRandomTestUser();
|
||||
user2 = dataUser.createRandomTestUser();
|
||||
userMultiSite = dataUser.createRandomTestUser();
|
||||
|
||||
siteModel1 = dataSite.usingUser(user1).createPrivateRandomSite();
|
||||
siteModel2 = dataSite.usingUser(user2).createPrivateRandomSite();
|
||||
|
||||
dataUser.addUserToSite(user2, siteModel1, UserRole.SiteContributor);
|
||||
dataUser.addUserToSite(userMultiSite, siteModel1, UserRole.SiteContributor);
|
||||
dataUser.addUserToSite(userMultiSite, siteModel2, UserRole.SiteContributor);
|
||||
|
||||
file0 = createContent(FILE_0_NAME, "This is the first test containing " + UNIQUE_WORD, siteModel1, user1);
|
||||
FileModel file1 = createContent(FILE_1_NAME, "This is another TEST file containing " + UNIQUE_WORD, siteModel1, user1);
|
||||
FileModel file2 = createContent(FILE_2_NAME, "This Test file is owned by user2 " + UNIQUE_WORD, siteModel1, user2);
|
||||
FileModel file3 = uploadDocument("test-data/alfresco-logo.png", user1, siteModel1);
|
||||
file3Name = file3.getName();
|
||||
|
||||
fileCreationDates = getCreationDates(file0, file1, file2);
|
||||
|
||||
// Remove user 2 from site, but he keeps ownership on FILE_2_NAME.
|
||||
dataUser.removeUserFromSite(user2, siteModel1);
|
||||
// Also create another file that only user 2 has access to.
|
||||
createContent(USER_2_FILE_NAME, "This is a test file that user1 does not have access to, but it still contains " + UNIQUE_WORD, siteModel2, user2);
|
||||
|
||||
dataContent.usingUser(user1).usingSite(siteModel1).createFolder(new FolderModel(FOLDER_0_NAME));
|
||||
dataContent.usingUser(user1).usingSite(siteModel1).createFolder(new FolderModel(FOLDER_1_NAME));
|
||||
|
||||
// Pre-build scoping predicates used by the negative / open-range tests below.
|
||||
user1FilesScope = "cmis:name IN ('" + FILE_0_NAME + "', '" + FILE_1_NAME + "', '" + FILE_2_NAME + "', '" + file3Name + "')";
|
||||
file3Scope = "D.cmis:name = '" + file3Name + "'";
|
||||
|
||||
STEP("Wait for batch indexer to index the last-created node (FOLDER_1_NAME)");
|
||||
SearchRequest probe = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:name = '" + FOLDER_1_NAME + "'");
|
||||
Utility.sleep(500, 60000, () -> searchQueryService.expectResultsFromQuery(probe, user1, FOLDER_1_NAME));
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void basicQuery()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document");
|
||||
searchQueryService.expectResultsInclude(query, user1, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME, file3Name);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void objectIdQuery()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:objectId = '" + file0.getNodeRef() + "'");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void objectTypeIdQuery()
|
||||
{
|
||||
SearchRequest query1 = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:objectTypeId = 'cmis:folder'");
|
||||
searchQueryService.expectResultsInclude(query1, user1, "documentLibrary", FOLDER_0_NAME, FOLDER_1_NAME, user1.getUsername());
|
||||
|
||||
SearchRequest query2 = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:objectTypeId = 'F:st:site'");
|
||||
searchQueryService.expectResultsInclude(query2, user1, siteModel1.getId());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void baseTypeIdQuery()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:baseTypeId = 'cmis:folder'");
|
||||
searchQueryService.expectResultsInclude(query, user1, "documentLibrary", FOLDER_0_NAME, FOLDER_1_NAME, user1.getUsername(), siteModel1.getId());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void isNullQuery()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:description IS NULL");
|
||||
searchQueryService.expectResultsInclude(query, user1, "documentLibrary", FOLDER_0_NAME, FOLDER_1_NAME, user1.getUsername());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void isNotNullQuery()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:description IS NOT NULL");
|
||||
searchQueryService.expectResultsInclude(query, user1, siteModel1.getId());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void matchNamesLikePrefix()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:name LIKE '" + PREFIX + "%'");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void matchNamesLikeSuffix()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:name LIKE '%" + SUFFIX + "'");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void matchContentOfFile()
|
||||
{
|
||||
// Check the query is case insensitive.
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE CONTAINS('" + UNIQUE_WORD + "')");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkPermissionForUser2()
|
||||
{
|
||||
// Reuse the prefix query to check which documents user2 can access.
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:name LIKE '" + PREFIX + "%'");
|
||||
searchQueryService.expectResultsFromQuery(query, user2, FILE_2_NAME, USER_2_FILE_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void matchDocumentName()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:name = '" + FILE_0_NAME + "'");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void doesNotMatchDocumentName()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE " + user1FilesScope + " AND cmis:name <> '" + FILE_0_NAME + "'");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_1_NAME, FILE_2_NAME, file3Name);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkOrderByAscSyntax()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT cmis:name FROM cmis:document WHERE cmis:name IN('" + FILE_0_NAME + "','" + FILE_1_NAME + "','" + FILE_2_NAME + "') ORDER BY cmis:name ASC");
|
||||
searchQueryService.expectResultsInOrder(query, user1, true, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkOrderByDescSyntax()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT cmis:name FROM cmis:document WHERE cmis:name IN('" + FILE_0_NAME + "','" + FILE_1_NAME + "','" + FILE_2_NAME + "') ORDER BY cmis:name DESC");
|
||||
searchQueryService.expectResultsInOrder(query, user1, false, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkInSyntax()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:name IN ('" + FILE_0_NAME + "', '" + FILE_1_NAME + "')");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkNotInSyntax()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE " + user1FilesScope + " AND cmis:name NOT IN ('" + FILE_0_NAME + "', '" + FILE_1_NAME + "')");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_2_NAME, file3Name);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkAfterDateSyntax()
|
||||
{
|
||||
String file0CreationDate = fileCreationDates.get(0);
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:creationDate > TIMESTAMP '" + file0CreationDate + "'");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_1_NAME, FILE_2_NAME, file3Name);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkAfterOrSameDateSyntax()
|
||||
{
|
||||
String file0CreationDate = fileCreationDates.get(0);
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:creationDate >= TIMESTAMP '" + file0CreationDate + "'");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME, file3Name);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkBeforeDateSyntax()
|
||||
{
|
||||
String file2CreationDate = fileCreationDates.get(2);
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE " + user1FilesScope + " AND cmis:creationDate < TIMESTAMP '" + file2CreationDate + "'");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkBeforeOrSameDateSyntax()
|
||||
{
|
||||
String file2CreationDate = fileCreationDates.get(2);
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE " + user1FilesScope + " AND cmis:creationDate <= TIMESTAMP '" + file2CreationDate + "'");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME, FILE_2_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkMatchesDateSyntax()
|
||||
{
|
||||
String file0CreationDate = fileCreationDates.get(0);
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:creationDate = TIMESTAMP '" + file0CreationDate + "'");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkDoesNotMatchDateSyntax()
|
||||
{
|
||||
String file0CreationDate = fileCreationDates.get(0);
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE " + user1FilesScope + " AND cmis:creationDate <> TIMESTAMP '" + file0CreationDate + "'");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_1_NAME, FILE_2_NAME, file3Name);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkInDatesSyntax()
|
||||
{
|
||||
String file0CreationDate = fileCreationDates.get(0);
|
||||
String file1CreationDate = fileCreationDates.get(1);
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE cmis:creationDate IN (TIMESTAMP '" + file0CreationDate + "', TIMESTAMP '" + file1CreationDate + "')");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FILE_1_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkNotInDatesSyntax()
|
||||
{
|
||||
String file0CreationDate = fileCreationDates.get(0);
|
||||
String file1CreationDate = fileCreationDates.get(1);
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document WHERE " + user1FilesScope + " AND cmis:creationDate NOT IN (TIMESTAMP '" + file0CreationDate + "', TIMESTAMP '" + file1CreationDate + "')");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_2_NAME, file3Name);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkEqualIntegerSyntax()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension = " + X_DIMENSION);
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkDifferentThanIntegerSyntax()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension <> " + X_DIMENSION);
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectNoResultsFromQuery(query, user1);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkGreaterThanIntegerSyntax()
|
||||
{
|
||||
int lessThanX = X_DIMENSION - 1;
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension > " + lessThanX);
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
|
||||
|
||||
query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension > " + X_DIMENSION);
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectNoResultsFromQuery(query, user1);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkGreaterThanOrEqualIntegerSyntax()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension >= " + X_DIMENSION);
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
|
||||
|
||||
int moreThanX = X_DIMENSION + 1;
|
||||
query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension >= " + moreThanX);
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectNoResultsFromQuery(query, user1);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkLessThanIntegerSyntax()
|
||||
{
|
||||
int moreThanX = X_DIMENSION + 1;
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension < " + moreThanX);
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
|
||||
|
||||
query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension < " + X_DIMENSION);
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectNoResultsFromQuery(query, user1);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkLessThanOrEqualIntegerSyntax()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension <= " + X_DIMENSION);
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
|
||||
|
||||
int lessThanX = X_DIMENSION - 1;
|
||||
query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension <= " + lessThanX);
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectNoResultsFromQuery(query, user1);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkInIntegersSyntax()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension IN (" + X_DIMENSION + ", " + 0 + ")");
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
|
||||
|
||||
query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE E.exif:pixelXDimension IN (" + 0 + ", " + -1 + ")");
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectNoResultsFromQuery(query, user1);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void checkNotInIntegersSyntax()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension NOT IN (" + X_DIMENSION + ", " + 0 + ")");
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectNoResultsFromQuery(query, user1);
|
||||
|
||||
query = req("cmis", "SELECT * FROM cmis:document AS D JOIN exif:exif AS E ON D.cmis:objectId = E.cmis:objectId WHERE " + file3Scope + " AND E.exif:pixelXDimension NOT IN (" + 0 + ", " + -1 + ")");
|
||||
query.setInclude(List.of("properties"));
|
||||
searchQueryService.expectResultsFromQuery(query, user1, file3Name);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void negative_basicCMISQuery_missingFrom()
|
||||
{
|
||||
// note: ideally 400 but currently 500 (also for Solr) :-(
|
||||
|
||||
SearchRequest query1 = req("cmis", "SELECT *");
|
||||
searchQueryService.expectErrorFromQuery(query1, user1, HttpStatus.INTERNAL_SERVER_ERROR, "expecting FROM");
|
||||
|
||||
SearchRequest query2 = req("cmis", "SELECT * FROM");
|
||||
searchQueryService.expectErrorFromQuery(query2, user1, HttpStatus.INTERNAL_SERVER_ERROR, "no viable alternative at input");
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void negative_basicCMISQuery_invalidType()
|
||||
{
|
||||
// note: ideally 400 but currently 500 (also for Solr) :-(
|
||||
SearchRequest query = req("SELECT * FROM cmis:unknown");
|
||||
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown property: {http://www.alfresco.org/model/content/1.0}cmis");
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void negative_objectTypeIdQuery_invalidType()
|
||||
{
|
||||
// note: ideally 400 but currently 500 (also for Solr) :-(
|
||||
SearchRequest query = req("SELECT * FROM cmis:folder WHERE cmis:objectTypeId = 'unknown:site'");
|
||||
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown property: {http://www.alfresco.org/model/content/1.0}cmis");
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void negative_isNullQuery_invalidField()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:unknown IS NULL");
|
||||
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown column/property cmis:unknown");
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void negative_isNotNullQuery_invalidField()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder WHERE cmis:unknown IS NOT NULL");
|
||||
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown column/property cmis:unknown");
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void negative_basicCMISQuery_invalidFieldName()
|
||||
{
|
||||
// note: ideally 400 but currently 500 (also for Solr) :-(
|
||||
|
||||
SearchRequest query1 = req("SELECT cmis:unknown FROM cmis:document");
|
||||
searchQueryService.expectErrorFromQuery(query1, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown property: {http://www.alfresco.org/model/content/1.0}cmis");
|
||||
|
||||
SearchRequest query2 = req("SELECT cm:unknown FROM cmis:document");
|
||||
searchQueryService.expectErrorFromQuery(query2, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown property: {http://www.alfresco.org/model/content/1.0}cm");
|
||||
|
||||
SearchRequest query3 = req("SELECT my:custom FROM cmis:document");
|
||||
searchQueryService.expectErrorFromQuery(query3, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown property: {http://www.alfresco.org/model/content/1.0}my");
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void negative_baseTypeIdQuery_invalidType()
|
||||
{
|
||||
// note: ideally 400 but currently 500 (also for Solr) :-(
|
||||
SearchRequest query = req("SELECT * FROM cmis:folder WHERE cmis:baseTypeId = 'cmis:unknown'");
|
||||
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Unknown property: {http://www.alfresco.org/model/content/1.0}cmis");
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void selectFolders()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:folder");
|
||||
searchQueryService.expectResultsInclude(query, user1,
|
||||
// We expect the site and the document library, ...
|
||||
siteModel1.getId(), "documentLibrary",
|
||||
// the two folders, ...
|
||||
FOLDER_0_NAME, FOLDER_1_NAME,
|
||||
// and the user home for the user performing the query.
|
||||
user1.getUsername());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void selectItems()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cmis:item");
|
||||
// This error is consistent with the way the DB and Solr handle the request.
|
||||
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "Type is not queryable cmis:item");
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void selectObjects()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cm:cmobject WHERE cmis:name IN ('" + FILE_0_NAME + "', '" + FOLDER_0_NAME + "')");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME, FOLDER_0_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void selectPeople()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT * FROM cm:person");
|
||||
searchQueryService.expectNodeTypesFromQuery(query, user1, "cm:person");
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void tableAlias()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT d.cmis:name FROM cmis:document d WHERE d.cmis:name IN ('" + FILE_0_NAME + "')");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void tableAliasWithAs()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT d.cmis:name FROM cmis:document AS d WHERE d.cmis:name IN ('" + FILE_0_NAME + "')");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void tableAlias_mismatchedAliasInSelect()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT z.cmis:name FROM cmis:document d WHERE d.cmis:name IN ('" + FILE_0_NAME + "')");
|
||||
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "No selector for z");
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void tableAlias_mismatchedAliasInWhere()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT d.cmis:name FROM cmis:document d WHERE z.cmis:name IN ('" + FILE_0_NAME + "')");
|
||||
searchQueryService.expectErrorFromQuery(query, user1, HttpStatus.INTERNAL_SERVER_ERROR, "No selector for z");
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void tableJoin()
|
||||
{
|
||||
SearchRequest query = req("cmis", "SELECT t.*, d.* FROM cm:titled t JOIN cmis:document d ON t.cmis:objectId = d.cmis:objectId WHERE d.cmis:name = '" + FILE_0_NAME + "'");
|
||||
searchQueryService.expectResultsFromQuery(query, user1, FILE_0_NAME);
|
||||
}
|
||||
|
||||
private FileModel createContent(String filename, String content, SiteModel site, UserModel user)
|
||||
{
|
||||
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
|
||||
return dataContent.usingUser(user).usingSite(site)
|
||||
.createContent(fileModel);
|
||||
}
|
||||
|
||||
private List<String> getCreationDates(FileModel... files)
|
||||
{
|
||||
return getCreationDates(user1, siteModel1, files);
|
||||
}
|
||||
|
||||
private List<String> getCreationDates(UserModel user, SiteModel site, FileModel... files)
|
||||
{
|
||||
return List.of(files)
|
||||
.stream()
|
||||
.map(FileModel::getCmisLocation)
|
||||
.map(cmisLocation -> dataContent.usingUser(user).usingSite(site).getCMISDocument(cmisLocation))
|
||||
.map(cmisDocument -> cmisDocument.<GregorianCalendar> getProperty("cmis:creationDate"))
|
||||
.map(creationDateProperty -> creationDateProperty.<GregorianCalendar> getValue())
|
||||
.map(GregorianCalendar::toInstant)
|
||||
.map(Instant::toString)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
private FileModel uploadDocument(String filePath, UserModel user, SiteModel site) throws IOException
|
||||
{
|
||||
ClassPathResource toUpload = new ClassPathResource(filePath);
|
||||
return dataContent.usingUser(user)
|
||||
.usingSite(site)
|
||||
.uploadDocument(toUpload.getFile());
|
||||
}
|
||||
}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
import static org.alfresco.utility.data.RandomData.getRandomName;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.dataprep.CMISUtil;
|
||||
import org.alfresco.rest.core.RestWrapper;
|
||||
import org.alfresco.rest.model.RestCategoryLinkBodyModel;
|
||||
import org.alfresco.rest.model.RestCategoryModel;
|
||||
import org.alfresco.rest.model.RestCategoryModelsCollection;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.Utility;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.RepoTestModel;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
public class ElasticsearchCategoriesCountTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
protected static final String INCLUDE_COUNT_PARAM = "count";
|
||||
protected static final String ROOT_CATEGORY_ID = "-root-";
|
||||
protected static final String CATEGORY_NAME_PREFIX = "CategoryName";
|
||||
protected static final String FIELD_NAME = "name";
|
||||
protected static final String FIELD_ID = "id";
|
||||
protected static final String FIELD_COUNT = "count";
|
||||
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
@Autowired
|
||||
private DataSite dataSite;
|
||||
@Autowired
|
||||
private DataContent dataContent;
|
||||
@Autowired
|
||||
private RestWrapper restClient;
|
||||
|
||||
private UserModel user;
|
||||
private SiteModel site;
|
||||
private RestCategoryModel categoryLinkedWithFolder;
|
||||
private RestCategoryModel categoryLinkedWithFile;
|
||||
private RestCategoryModel categoryLinkedWithBoth;
|
||||
private RestCategoryModel notLinkedCategory;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation() throws InterruptedException
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
STEP("Create user and site");
|
||||
user = dataUser.createRandomTestUser();
|
||||
site = dataSite.usingUser(user).createPublicRandomSite();
|
||||
|
||||
STEP("Create a folder, file in it and few categories");
|
||||
final FolderModel folder = dataContent.usingUser(user).usingSite(site).createFolder();
|
||||
final FileModel file = dataContent.usingUser(user).usingResource(folder).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
|
||||
categoryLinkedWithFolder = prepareCategoryUnderRoot();
|
||||
categoryLinkedWithFile = prepareCategoryUnderRoot();
|
||||
categoryLinkedWithBoth = prepareCategoryUnder(prepareCategoryUnderRoot());
|
||||
notLinkedCategory = prepareCategoryUnderRoot();
|
||||
|
||||
STEP("Link folder and file to categories");
|
||||
linkContentToCategories(folder, categoryLinkedWithFolder, categoryLinkedWithBoth);
|
||||
linkContentToCategories(file, categoryLinkedWithFile, categoryLinkedWithBoth);
|
||||
|
||||
STEP("Wait for indexing to complete");
|
||||
Utility.sleep(500, 60000, () -> restClient.authenticateUser(user)
|
||||
.withCoreAPI()
|
||||
.usingCategory(categoryLinkedWithBoth)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.getCategory()
|
||||
.assertThat()
|
||||
.field(FIELD_COUNT)
|
||||
.is(2));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public void dataCleanup()
|
||||
{
|
||||
STEP("Remove site and user");
|
||||
dataSite.usingUser(user).deleteSite(site);
|
||||
dataUser.deleteUser(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify count for a category linked with file and folder.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API})
|
||||
public void testGetCategoryById_includeCount()
|
||||
{
|
||||
STEP("Get linked category and verify if count is higher than 0");
|
||||
final RestCategoryModel actualCategory = restClient.authenticateUser(user)
|
||||
.withCoreAPI()
|
||||
.usingCategory(categoryLinkedWithBoth)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.getCategory();
|
||||
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
actualCategory.assertThat().field(FIELD_ID).is(categoryLinkedWithBoth.getId());
|
||||
actualCategory.assertThat().field(FIELD_COUNT).is(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify count for a category not linked with any content.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API})
|
||||
public void testGetCategoryById_includeCountForNonLinkedCategory()
|
||||
{
|
||||
STEP("Get non-linked category and verify if count is 0");
|
||||
final RestCategoryModel actualCategory = restClient.authenticateUser(user)
|
||||
.withCoreAPI()
|
||||
.usingCategory(notLinkedCategory)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.getCategory();
|
||||
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
actualCategory.assertThat().field(FIELD_ID).is(notLinkedCategory.getId());
|
||||
actualCategory.assertThat().field(FIELD_COUNT).is(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify count for three categories: linked with file, linked with folder and third not linked to any content.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API})
|
||||
public void testGetCategories_includeCount()
|
||||
{
|
||||
STEP("Get few categories and verify its counts");
|
||||
final RestCategoryModel parentCategory = createCategoryModelWithId(ROOT_CATEGORY_ID);
|
||||
final RestCategoryModelsCollection actualCategories = restClient.authenticateUser(user)
|
||||
.withCoreAPI()
|
||||
.usingCategory(parentCategory)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.getCategoryChildren();
|
||||
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
assertTrue(actualCategories.getEntries().stream()
|
||||
.map(RestCategoryModel::onModel)
|
||||
.anyMatch(category -> category.getId().equals(categoryLinkedWithFolder.getId()) && category.getCount() == 1));
|
||||
assertTrue(actualCategories.getEntries().stream()
|
||||
.map(RestCategoryModel::onModel)
|
||||
.anyMatch(category -> category.getId().equals(categoryLinkedWithFile.getId()) && category.getCount() == 1));
|
||||
assertTrue(actualCategories.getEntries().stream()
|
||||
.map(RestCategoryModel::onModel)
|
||||
.anyMatch(category -> category.getId().equals(notLinkedCategory.getId()) && category.getCount() == 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create category and verify that its count is 0.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API})
|
||||
public void testCreateCategory_includingCount()
|
||||
{
|
||||
STEP("Create a category under root and verify if count is 0");
|
||||
final String categoryName = getRandomName("Category");
|
||||
final RestCategoryModel rootCategory = createCategoryModelWithId(ROOT_CATEGORY_ID);
|
||||
final RestCategoryModel aCategory = createCategoryModelWithName(categoryName);
|
||||
final RestCategoryModel createdCategory = restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.usingCategory(rootCategory)
|
||||
.createSingleCategory(aCategory);
|
||||
|
||||
STEP("Create a category under root category (as admin)");
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
createdCategory.assertThat().field(FIELD_NAME).is(categoryName);
|
||||
createdCategory.assertThat().field(FIELD_COUNT).is(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update category linked to file and folder and verify that its count is 2.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API})
|
||||
public void testUpdateCategory_includeCount()
|
||||
{
|
||||
STEP("Update linked category and verify if count is higher than 0");
|
||||
final String categoryNewName = getRandomName("NewCategoryName");
|
||||
final RestCategoryModel fixedCategoryModel = createCategoryModelWithName(categoryNewName);
|
||||
final RestCategoryModel updatedCategory = restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.usingCategory(categoryLinkedWithBoth)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.updateCategory(fixedCategoryModel);
|
||||
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
updatedCategory.assertThat().field(FIELD_ID).is(categoryLinkedWithBoth.getId());
|
||||
updatedCategory.assertThat().field(FIELD_COUNT).is(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update category not linked to any content and verify that its count is 0.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API})
|
||||
public void testUpdateCategory_includeCountForNonLinkedCategory()
|
||||
{
|
||||
STEP("Update non-linked category and verify if count is 0");
|
||||
final String categoryNewName = getRandomName("NewCategoryName");
|
||||
final RestCategoryModel fixedCategoryModel = createCategoryModelWithName(categoryNewName);
|
||||
final RestCategoryModel updatedCategory = restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.usingCategory(notLinkedCategory)
|
||||
.include(INCLUDE_COUNT_PARAM)
|
||||
.updateCategory(fixedCategoryModel);
|
||||
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
updatedCategory.assertThat().field(FIELD_ID).is(notLinkedCategory.getId());
|
||||
updatedCategory.assertThat().field(FIELD_COUNT).is(0);
|
||||
}
|
||||
|
||||
private RestCategoryModelsCollection linkContentToCategories(final RepoTestModel node, final RestCategoryModel... categories)
|
||||
{
|
||||
final List<RestCategoryLinkBodyModel> categoryLinkModels = Arrays.stream(categories)
|
||||
.map(RestCategoryModel::getId)
|
||||
.map(this::createCategoryLinkModelWithId)
|
||||
.collect(Collectors.toList());
|
||||
final RestCategoryModelsCollection linkedCategories = restClient.authenticateUser(user).withCoreAPI().usingNode(node).linkToCategories(categoryLinkModels);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
|
||||
return linkedCategories;
|
||||
}
|
||||
|
||||
private RestCategoryModel prepareCategoryUnderRoot()
|
||||
{
|
||||
return prepareCategoryUnder(createCategoryModelWithId(ROOT_CATEGORY_ID));
|
||||
}
|
||||
|
||||
private RestCategoryModel prepareCategoryUnder(final RestCategoryModel parentCategory)
|
||||
{
|
||||
final RestCategoryModel categoryModel = createCategoryModelWithName(getRandomName(CATEGORY_NAME_PREFIX));
|
||||
final RestCategoryModel createdCategory = restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withCoreAPI()
|
||||
.usingCategory(parentCategory)
|
||||
.createSingleCategory(categoryModel);
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
|
||||
return createdCategory;
|
||||
}
|
||||
|
||||
private RestCategoryModel createCategoryModelWithId(final String id)
|
||||
{
|
||||
return createCategoryModelWithIdAndName(id, null);
|
||||
}
|
||||
|
||||
private RestCategoryModel createCategoryModelWithName(final String name)
|
||||
{
|
||||
return createCategoryModelWithIdAndName(null, name);
|
||||
}
|
||||
|
||||
private RestCategoryModel createCategoryModelWithIdAndName(final String id, final String name)
|
||||
{
|
||||
return RestCategoryModel.builder()
|
||||
.id(id)
|
||||
.name(name)
|
||||
.create();
|
||||
}
|
||||
|
||||
private RestCategoryLinkBodyModel createCategoryLinkModelWithId(final String id)
|
||||
{
|
||||
return RestCategoryLinkBodyModel.builder()
|
||||
.categoryId(id)
|
||||
.create();
|
||||
}
|
||||
}
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.dataprep.CMISUtil;
|
||||
import org.alfresco.rest.core.RestWrapper;
|
||||
import org.alfresco.rest.model.RestTagModel;
|
||||
import org.alfresco.rest.model.RestTagModelsCollection;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.Utility;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.data.RandomData;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
|
||||
/**
|
||||
* Tests to verify batch indexing of tags using Elasticsearch.
|
||||
*/
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
public class ElasticsearchGetTagsTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
@Autowired
|
||||
private DataSite dataSite;
|
||||
@Autowired
|
||||
private DataContent dataContent;
|
||||
@Autowired
|
||||
private RestWrapper restClient;
|
||||
|
||||
private UserModel user;
|
||||
private SiteModel site;
|
||||
private RestTagModel apple, banana, pineapple, winegrape, grapefruit, orange;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation() throws InterruptedException
|
||||
{
|
||||
serverHealth.isServerReachable();
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
STEP("Create user and site");
|
||||
user = dataUser.createRandomTestUser();
|
||||
site = dataSite.usingUser(user).createPublicRandomSite();
|
||||
final FileModel document = dataContent.usingAdmin().usingSite(site).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
|
||||
|
||||
STEP("Create few tags");
|
||||
apple = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
|
||||
.createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("apple")).create());
|
||||
banana = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
|
||||
.createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("banana")).create());
|
||||
pineapple = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
|
||||
.createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("pineapple")).create());
|
||||
winegrape = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
|
||||
.createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("winegrape")).create());
|
||||
grapefruit = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
|
||||
.createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("grapefruit")).create());
|
||||
orange = restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
|
||||
.createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("orange")).create());
|
||||
|
||||
STEP("Wait for indexing to complete");
|
||||
Utility.sleep(500, 60000, () -> restClient.authenticateUser(dataUser.getAdminUser())
|
||||
.withParams("where=(tag MATCHES ('oran*'))")
|
||||
.withCoreAPI()
|
||||
.getTags()
|
||||
.assertThat()
|
||||
.entrySetContains("tag", orange.getTag().toLowerCase()));
|
||||
|
||||
// Step 2: now attach apple to document - tag node is guaranteed to be in ES
|
||||
STEP("Attach apple tag to document");
|
||||
restClient.authenticateUser(user).withCoreAPI().usingResource(document)
|
||||
.addTag(apple.getTag());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public void dataCleanup()
|
||||
{
|
||||
STEP("Remove created tags");
|
||||
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(apple).deleteTag();
|
||||
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(banana).deleteTag();
|
||||
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(pineapple).deleteTag();
|
||||
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(winegrape).deleteTag();
|
||||
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(grapefruit).deleteTag();
|
||||
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI().usingTag(orange).deleteTag();
|
||||
|
||||
STEP("Remove site and user");
|
||||
dataSite.usingUser(user).deleteSite(site);
|
||||
dataUser.deleteUser(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if exact name filter can be applied.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
|
||||
public void testGetTags_withSingleNameFilter()
|
||||
{
|
||||
STEP("Get tags with names filter using EQUALS and expect one item in result");
|
||||
final RestTagModelsCollection returnedCollection = restClient.authenticateUser(user)
|
||||
.withParams("where=(tag='" + apple.getTag() + "')")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||
returnedCollection.assertThat()
|
||||
.entrySetMatches("tag", Set.of(apple.getTag().toLowerCase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if multiple names can be applied as a filter.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
|
||||
public void testGetTags_withTwoNameFilters()
|
||||
{
|
||||
STEP("Get tags with names filter using IN and expect two items in result");
|
||||
final RestTagModelsCollection returnedCollection = restClient.authenticateUser(user)
|
||||
.withParams("where=(tag IN ('" + apple.getTag() + "', '" + banana.getTag() + "'))")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||
returnedCollection.assertThat()
|
||||
.entrySetMatches("tag", Set.of(apple.getTag().toLowerCase(), banana.getTag().toLowerCase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if alike name filter can be applied.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
|
||||
public void testGetTags_whichNamesStartsWithOrphan()
|
||||
{
|
||||
STEP("Get tags with names filter using MATCHES and expect one item in result");
|
||||
final RestTagModelsCollection returnedCollection = restClient.authenticateUser(user)
|
||||
.withParams("where=(tag MATCHES ('*an*'))")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||
returnedCollection.assertThat()
|
||||
.entrySetContains("tag", banana.getTag().toLowerCase(), orange.getTag().toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that tags can be filtered by exact name and alike name at the same time.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
|
||||
public void testGetTags_withExactNameAndAlikeFilters()
|
||||
{
|
||||
STEP("Get tags with names filter using EQUALS and MATCHES and expect three items in result");
|
||||
final RestTagModelsCollection returnedCollection = restClient.authenticateUser(user)
|
||||
.withParams("where=(tag='" + orange.getTag() + "' OR tag MATCHES ('*grape*'))")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||
returnedCollection.assertThat()
|
||||
.entrySetMatches("tag", Set.of(orange.getTag().toLowerCase(), grapefruit.getTag().toLowerCase(), winegrape.getTag().toLowerCase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if multiple alike filters can be applied.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
|
||||
public void testGetTags_withTwoAlikeFilters()
|
||||
{
|
||||
STEP("Get tags applying names filter using MATCHES twice and expect three items in result");
|
||||
final RestTagModelsCollection returnedCollection = restClient.authenticateUser(user)
|
||||
.withParams("where=(tag MATCHES ('*apple*') OR tag MATCHES ('grape*'))")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.OK);
|
||||
returnedCollection.assertThat()
|
||||
.entrySetMatches("tag", Set.of(apple.getTag().toLowerCase(), pineapple.getTag().toLowerCase(), grapefruit.getTag().toLowerCase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that providing incorrect field name in where query will result with 400 (Bad Request).
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
|
||||
public void testGetTags_withWrongWherePropertyNameAndExpect400()
|
||||
{
|
||||
STEP("Try to get tags with names filter using EQUALS and wrong property name and expect 400");
|
||||
restClient.authenticateUser(user)
|
||||
.withParams("where=(name=apple)")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST)
|
||||
.assertLastError().containsSummary("Where query error: property with name: name is not expected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify tht AND operator is not supported in where query and expect 400 (Bad Request).
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
|
||||
public void testGetTags_queryAndOperatorNotSupported()
|
||||
{
|
||||
STEP("Try to get tags applying names filter using AND operator and expect 400");
|
||||
restClient.authenticateUser(user)
|
||||
.withParams("where=(tag=apple AND tag IN ('banana', 'melon'))")
|
||||
.withCoreAPI()
|
||||
.getTags();
|
||||
|
||||
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST)
|
||||
.assertLastError().containsSummary("An invalid WHERE query was received. Unsupported Predicate");
|
||||
}
|
||||
}
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static java.util.function.Predicate.not;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static java.util.stream.Stream.concat;
|
||||
import static java.util.stream.Stream.of;
|
||||
|
||||
import static org.apache.http.entity.ContentType.APPLICATION_JSON;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.gson.Gson;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.dataprep.AlfrescoHttpClient;
|
||||
import org.alfresco.dataprep.AlfrescoHttpClientFactory;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
public class ElasticsearchIsUnsetTest extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
private static final String CM_TITLED_ASPECT = "cm:titled";
|
||||
private static final String CM_TITLE_PROPERTY = "cm:title";
|
||||
private static final String CM_DESCRIPTION_PROPERTY = "cm:description";
|
||||
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@Autowired
|
||||
SearchQueryService searchQueryService;
|
||||
|
||||
@Autowired
|
||||
private AlfrescoHttpClientFactory alfrescoHttpClientFactory;
|
||||
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
|
||||
private AlfrescoHttpClient alfrescoClient;
|
||||
private AlfNode testNode;
|
||||
|
||||
@BeforeClass
|
||||
public void setUp() throws IOException
|
||||
{
|
||||
alfrescoClient = alfrescoHttpClientFactory.getObject();
|
||||
testNode = createNodeWithTitleButWithoutDescription();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public void tearDown()
|
||||
{
|
||||
alfrescoClient.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testElasticsearchIsUnsetQuery() throws Exception
|
||||
{
|
||||
assertNodeIsReadyForTesting();
|
||||
|
||||
// cm:title is set, we should have no result
|
||||
shouldNotFindNode("ISUNSET:\"" + CM_TITLE_PROPERTY + "\"");
|
||||
shouldFindNode("NOT ISUNSET:\"" + CM_TITLE_PROPERTY + "\"");
|
||||
|
||||
// cm:description is not set, we should have a result
|
||||
shouldFindNode("ISUNSET:\"" + CM_DESCRIPTION_PROPERTY + "\"");
|
||||
shouldNotFindNode("NOT ISUNSET:\"" + CM_DESCRIPTION_PROPERTY + "\"");
|
||||
|
||||
removeCmTitledAspect();
|
||||
|
||||
// ISUNSET is type aware, no cm:titled aspect so no result
|
||||
shouldNotFindNode("ISUNSET:\"" + CM_TITLE_PROPERTY + "\"");
|
||||
shouldNotFindNode("ISUNSET:\"" + CM_DESCRIPTION_PROPERTY + "\"");
|
||||
shouldFindNode("NOT ISUNSET:\"" + CM_TITLE_PROPERTY + "\"");
|
||||
shouldFindNode("NOT ISUNSET:\"" + CM_DESCRIPTION_PROPERTY + "\"");
|
||||
|
||||
restoreCmTitledAspect();
|
||||
|
||||
// cm:titled aspect is back but cm:title property has been "unset"
|
||||
shouldFindNode("ISUNSET:\"" + CM_TITLE_PROPERTY + "\"");
|
||||
shouldFindNode("ISUNSET:\"" + CM_DESCRIPTION_PROPERTY + "\"");
|
||||
shouldNotFindNode("NOT ISUNSET:\"" + CM_TITLE_PROPERTY + "\"");
|
||||
shouldNotFindNode("NOT ISUNSET:\"" + CM_DESCRIPTION_PROPERTY + "\"");
|
||||
}
|
||||
|
||||
private void restoreCmTitledAspect() throws IOException
|
||||
{
|
||||
setAspects(testNode.getAspects());
|
||||
}
|
||||
|
||||
private void removeCmTitledAspect() throws IOException
|
||||
{
|
||||
final List<String> noCmTitled = testNode.getAspects().stream().filter(not(CM_TITLED_ASPECT::equals)).toList();
|
||||
setAspects(noCmTitled);
|
||||
}
|
||||
|
||||
private void assertNodeIsReadyForTesting()
|
||||
{
|
||||
assertThat(testNode.getAspects()).contains(CM_TITLED_ASPECT);
|
||||
shouldFindNode();
|
||||
}
|
||||
|
||||
private void shouldFindNode(String... conjunctions)
|
||||
{
|
||||
searchQueryService.expectResultsInclude(searchByName(conjunctions), dataUser.getAdminUser(), testNode.getName());
|
||||
}
|
||||
|
||||
private void shouldNotFindNode(String... conjunctions)
|
||||
{
|
||||
searchQueryService.expectNoResultsFromQuery(searchByName(conjunctions), dataUser.getAdminUser());
|
||||
}
|
||||
|
||||
private SearchRequest searchByName(String... conjunctions)
|
||||
{
|
||||
return req(concat(of("cm:name:\"" + testNode.getName() + "\""), of(conjunctions)).collect(joining(" AND ")));
|
||||
}
|
||||
|
||||
private void setAspects(Collection<String> aspectsToSet) throws IOException
|
||||
{
|
||||
final URI nodeURI = URI.create(alfrescoClient.getApiVersionUrl()).resolve("nodes/" + testNode.getId());
|
||||
final HttpPut putRequest = new HttpPut(nodeURI);
|
||||
|
||||
final String jsonBody = toJsonString(Map.of("aspectNames", aspectsToSet));
|
||||
putRequest.setEntity(new StringEntity(jsonBody, APPLICATION_JSON));
|
||||
|
||||
nodeApiRequest(putRequest, 200);
|
||||
}
|
||||
|
||||
private AlfNode createNodeWithTitleButWithoutDescription() throws IOException
|
||||
{
|
||||
final URI parentURI = URI.create(alfrescoClient.getApiVersionUrl()).resolve("nodes/-my-/children");
|
||||
final HttpPost postRequest = new HttpPost(parentURI);
|
||||
final String jsonBody = toJsonString(
|
||||
Map.of("name", uniqueName(),
|
||||
"nodeType", "cm:content",
|
||||
"properties", Map.of("cm:title", "UnsetTestingDocument")));
|
||||
postRequest.setEntity(new StringEntity(jsonBody, APPLICATION_JSON));
|
||||
|
||||
return nodeApiRequest(postRequest, 201);
|
||||
}
|
||||
|
||||
private AlfNode nodeApiRequest(HttpRequestBase request, int expectedResponseCode) throws IOException
|
||||
{
|
||||
final HttpResponse response = alfrescoClient.executeAsAdmin(request);
|
||||
assertThat(response.getStatusLine().getStatusCode()).isEqualTo(expectedResponseCode);
|
||||
|
||||
final HttpEntity entity = response.getEntity();
|
||||
assertThat(entity).isNotNull();
|
||||
assertThat(entity.getContentType()).isNotNull();
|
||||
assertThat(entity.getContentType().getElements()).hasSize(1);
|
||||
|
||||
final HeaderElement contentTypeHeader = entity.getContentType().getElements()[0];
|
||||
assertThat(contentTypeHeader.getName()).isEqualTo(APPLICATION_JSON.getMimeType());
|
||||
|
||||
final Charset contentCharset = ofNullable(contentTypeHeader.getParameterByName("charset"))
|
||||
.map(NameValuePair::getValue)
|
||||
.map(Charset::forName)
|
||||
.orElse(StandardCharsets.UTF_8);
|
||||
|
||||
try (var reader = new InputStreamReader(entity.getContent(), contentCharset))
|
||||
{
|
||||
return asNode((Map<String, ?>) fromJsonString(CharStreams.toString(reader)).get("entry"));
|
||||
}
|
||||
}
|
||||
|
||||
private AlfNode asNode(Map<String, ?> map)
|
||||
{
|
||||
return new AlfNode(map);
|
||||
}
|
||||
|
||||
private String uniqueName()
|
||||
{
|
||||
return "Name-" + UUID.randomUUID();
|
||||
}
|
||||
|
||||
private String toJsonString(Map<String, ?> map)
|
||||
{
|
||||
return gson.toJson(map);
|
||||
}
|
||||
|
||||
private Map<String, ?> fromJsonString(String string)
|
||||
{
|
||||
return gson.fromJson(string, Map.class);
|
||||
}
|
||||
|
||||
private static class AlfNode
|
||||
{
|
||||
private final Map<String, ?> nodeMap;
|
||||
|
||||
private AlfNode(Map<String, ?> nodeMap)
|
||||
{
|
||||
this.nodeMap = requireNonNull(nodeMap);
|
||||
}
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return requireNonNull((String) nodeMap.get("id"));
|
||||
}
|
||||
|
||||
public Set<String> getAspects()
|
||||
{
|
||||
return ofNullable(nodeMap.get("aspectNames"))
|
||||
.map(c -> (Collection<String>) c)
|
||||
.map(Set::copyOf)
|
||||
.orElseGet(Set::of);
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return (String) nodeMap.get("name");
|
||||
}
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
import static org.alfresco.tas.TestDataUtility.getAlphabeticUUID;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.search.RestRequestLimitsModel;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.constants.UserRole;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml", initializers = AlfrescoStackInitializer.class)
|
||||
/**
|
||||
* In this test we are verifying the Track Total Hits feature
|
||||
*/
|
||||
public class ElasticsearchLimitTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
private static final String PREFIX = getAlphabeticUUID() + "_";
|
||||
private static final int TOTAL_DOCUMENT_COUNT = 20;
|
||||
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
|
||||
@Autowired
|
||||
private DataContent dataContent;
|
||||
|
||||
@Autowired
|
||||
private DataSite dataSite;
|
||||
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
|
||||
@Autowired
|
||||
protected SearchQueryService searchQueryService;
|
||||
|
||||
private UserModel userSite1;
|
||||
private SiteModel siteModel1;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
userSite1 = dataUser.createRandomTestUser();
|
||||
|
||||
siteModel1 = dataSite.usingUser(userSite1).createPrivateRandomSite();
|
||||
|
||||
dataUser.addUserToSite(userSite1, siteModel1, UserRole.SiteContributor);
|
||||
|
||||
for (int i = 0; i < TOTAL_DOCUMENT_COUNT; i++)
|
||||
{
|
||||
createContent(PREFIX + i + ".txt", "Document " + i, siteModel1, userSite1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchUsingTotalHitsLimitDefaultValue()
|
||||
{
|
||||
SearchRequest request = req("cm:name:" + PREFIX + "*.txt");
|
||||
|
||||
// Without setting limits, we should get all up to 10k
|
||||
searchQueryService.expectTotalHitsFromQuery(request, userSite1, TOTAL_DOCUMENT_COUNT);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchUsingTotalHitsLimitZeroShouldDefault()
|
||||
{
|
||||
SearchRequest request = req("cm:name:" + PREFIX + "*.txt");
|
||||
|
||||
// Setting trackTotalHitsLimit to 0 should behave as not setting it, counting all up to 10k
|
||||
request.setLimits(new RestRequestLimitsModel(null, null, 0));
|
||||
searchQueryService.expectTotalHitsFromQuery(request, userSite1, TOTAL_DOCUMENT_COUNT);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchUsingTotalHitsLimit()
|
||||
{
|
||||
SearchRequest request = req("cm:name:" + PREFIX + "*.txt");
|
||||
|
||||
// Setting trackTotalHitsLimit to 10 should only count up to 10
|
||||
request.setLimits(new RestRequestLimitsModel(null, null, 10));
|
||||
searchQueryService.expectTotalHitsFromQuery(request, userSite1, 10);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchUsingTotalHitsUnlimited()
|
||||
{
|
||||
SearchRequest request = req("cm:name:" + PREFIX + "*.txt");
|
||||
|
||||
// Setting trackTotalHitsLimit to -1 should only count up to max int
|
||||
request.setLimits(new RestRequestLimitsModel(null, null, -1));
|
||||
searchQueryService.expectTotalHitsFromQuery(request, userSite1, TOTAL_DOCUMENT_COUNT);
|
||||
}
|
||||
|
||||
private FileModel createContent(String filename, String content, SiteModel site, UserModel user)
|
||||
{
|
||||
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
|
||||
return dataContent.usingUser(user).usingSite(site).createContent(fileModel);
|
||||
}
|
||||
}
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
import static org.alfresco.tas.TestDataUtility.getAlphabeticUUID;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.dataprep.AlfrescoHttpClient;
|
||||
import org.alfresco.dataprep.AlfrescoHttpClientFactory;
|
||||
import org.alfresco.rest.search.RestRequestQueryModel;
|
||||
import org.alfresco.rest.search.SearchNodeModel;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.constants.UserRole;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
/**
|
||||
* In this test we are verifying end-to-end the indexing and search in Elasticsearch. In order to test ACLs we created 2 sites and 3 users.
|
||||
*/
|
||||
public class ElasticsearchLiveIndexingTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
private static final String PREFIX = getAlphabeticUUID() + "_";
|
||||
private static final String UNIQUE_WORD = getAlphabeticUUID();
|
||||
private static final String FILE_0_NAME = PREFIX + "test.txt";
|
||||
private static final String FILE_1_NAME = PREFIX + "another.txt";
|
||||
private static final String FILE_2_NAME = PREFIX + "user1.txt";
|
||||
private static final String FILE_3_NAME = PREFIX + "user1Old.txt";
|
||||
public static final String BEFORE_1970_TXT = "before1970.txt";
|
||||
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
|
||||
@Autowired
|
||||
private DataContent dataContent;
|
||||
|
||||
@Autowired
|
||||
private DataSite dataSite;
|
||||
|
||||
@Autowired
|
||||
private AlfrescoHttpClientFactory alfrescoHttpClientFactory;
|
||||
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
|
||||
@Autowired
|
||||
protected SearchQueryService searchQueryService;
|
||||
|
||||
private UserModel userSite1;
|
||||
private UserModel userSite2;
|
||||
private UserModel userMultiSite;
|
||||
private SiteModel siteModel1;
|
||||
private SiteModel siteModel2;
|
||||
|
||||
/**
|
||||
* Data will be prepared using the schema below:
|
||||
* <p>
|
||||
* Site1: - Users: userSite1, userMultiSite - Documents: FILE_0_NAME (owner: userSite1), FILE_1_NAME (owner: userSite1), FILE_3_NAME (owner: userSite2)
|
||||
* <p>
|
||||
* Site2: - Users: userSite2, userMultiSite - Documents: FILE_2_NAME (owner: userSite2)
|
||||
*/
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
userSite1 = dataUser.createRandomTestUser();
|
||||
userSite2 = dataUser.createRandomTestUser();
|
||||
userMultiSite = dataUser.createRandomTestUser();
|
||||
|
||||
siteModel1 = dataSite.usingUser(userSite1).createPrivateRandomSite();
|
||||
siteModel2 = dataSite.usingUser(userSite2).createPrivateRandomSite();
|
||||
|
||||
dataUser.addUserToSite(userSite2, siteModel1, UserRole.SiteContributor);
|
||||
dataUser.addUserToSite(userMultiSite, siteModel1, UserRole.SiteContributor);
|
||||
dataUser.addUserToSite(userMultiSite, siteModel2, UserRole.SiteContributor);
|
||||
|
||||
createContent(FILE_0_NAME, "This is the first test containing the unique word " + UNIQUE_WORD, siteModel1, userSite1);
|
||||
createContent(FILE_1_NAME, "This is another TEST file", siteModel1, userSite1);
|
||||
createContent(FILE_2_NAME, "This is another test file", siteModel2, userSite2);
|
||||
createContent(FILE_3_NAME, "This is another Test file", siteModel1, userSite2);
|
||||
// remove the user from site, but he keeps ownership on FILE_3_NAME
|
||||
dataUser.removeUserFromSite(userSite2, siteModel1);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchCanFindAFileUsingIncludeParameter()
|
||||
{
|
||||
SearchRequest queryWithoutIncludes = req(UNIQUE_WORD);
|
||||
Predicate<SearchNodeModel> allFieldsNull = searchNodeModel -> searchNodeModel.getProperties() == null
|
||||
&& searchNodeModel.getPath() == null
|
||||
&& searchNodeModel.getAspectNames() == null
|
||||
&& searchNodeModel.getAllowableOperations() == null
|
||||
&& searchNodeModel.getPermissions() == null
|
||||
&& searchNodeModel.getAssociation() == null
|
||||
&& searchNodeModel.isLocked() == null
|
||||
&& searchNodeModel.isLink() == null;
|
||||
searchQueryService.expectAllResultsFromQuery(queryWithoutIncludes, userSite1, allFieldsNull);
|
||||
|
||||
SearchRequest queryWithIncludes = new SearchRequest();
|
||||
// A full list of all fields that can be included is declared in constant:
|
||||
// org.alfresco.rest.api.search.impl.SearchMapper.PERMITTED_INCLUDES
|
||||
queryWithIncludes.setInclude(asList("properties", "path", "aspectNames", "isLocked", "allowableOperations",
|
||||
"permissions", "isLink", "association"));
|
||||
RestRequestQueryModel queryReq = new RestRequestQueryModel();
|
||||
queryReq.setQuery(UNIQUE_WORD);
|
||||
queryWithIncludes.setQuery(queryReq);
|
||||
Predicate<SearchNodeModel> noFieldsNull = searchNodeModel -> searchNodeModel.getProperties() != null
|
||||
&& searchNodeModel.getPath() != null
|
||||
&& searchNodeModel.getAspectNames() != null
|
||||
&& searchNodeModel.getAllowableOperations() != null
|
||||
&& searchNodeModel.getPermissions() != null
|
||||
&& searchNodeModel.getAssociation() != null
|
||||
&& searchNodeModel.isLocked() != null
|
||||
&& searchNodeModel.isLink() != null;
|
||||
searchQueryService.expectAllResultsFromQuery(queryWithIncludes, userSite1, noFieldsNull);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchCanFindAFile()
|
||||
{
|
||||
// this test must found only one documents, while documents in the system are four because
|
||||
// only one contains the unique word.
|
||||
searchQueryService.expectResultsFromQuery(req(UNIQUE_WORD), userSite1, FILE_0_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchCanFindFilesOnASite()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req(PREFIX), userSite1, FILE_0_NAME, FILE_1_NAME, FILE_3_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchCanFindAFileOnMultipleSitesWithOwner()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req(PREFIX), userSite2, FILE_3_NAME, FILE_2_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchCanFindAFileOnMultipleSites()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req(PREFIX), userMultiSite, FILE_0_NAME, FILE_1_NAME, FILE_3_NAME, FILE_2_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void wildcardWorksInsideQuotes()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("cm:name:\"" + PREFIX + "user1*\""), userMultiSite, FILE_2_NAME, FILE_3_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void wildcardWorksWithoutQuotes()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("cm:name:" + PREFIX + "user1*"), userMultiSite, FILE_2_NAME, FILE_3_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH, enabled = false) // Test should be re-enabled within: ACS-6068
|
||||
public void wildcardNodeRefQuery()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("ANCESTOR:\"" + siteModel2.getGuid().substring(0, 10) + "*\""), userMultiSite, "documentLibrary", FILE_2_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void findFileWithRangeQuery()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("cm:created:[NOW-1YEAR TO MAX] AND name:" + FILE_0_NAME), userSite1, FILE_0_NAME);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void omitFileWithRangeQuery()
|
||||
{
|
||||
searchQueryService.expectNoResultsFromQuery(req("cm:created:[MIN TO NOW-2YEARS] AND name:" + FILE_0_NAME), userSite1);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void indexAndSearchForDateBefore1970()
|
||||
{
|
||||
// Elasticsearch doesn't accept numbers for dates before 1970, so we create and search for a specific document in order to verify that.
|
||||
createNodeWithProperties(siteModel1, new FileModel(BEFORE_1970_TXT, FileType.TEXT_PLAIN), userSite1,
|
||||
Map.of("cm:from", -2637887000L));
|
||||
|
||||
searchQueryService.expectResultsInclude(req("cm:from:1969-12-01T11:15:13Z"), userSite1, BEFORE_1970_TXT);
|
||||
}
|
||||
|
||||
private FileModel createContent(String filename, String content, SiteModel site, UserModel user)
|
||||
{
|
||||
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
|
||||
return dataContent.usingUser(user).usingSite(site)
|
||||
.createContent(fileModel);
|
||||
}
|
||||
|
||||
private void createNodeWithProperties(SiteModel parentSite, FileModel fileModel, UserModel currentUser, Map<String, Object> properties)
|
||||
{
|
||||
AlfrescoHttpClient client = alfrescoHttpClientFactory.getObject();
|
||||
String reqUrl = client.getApiVersionUrl() + "nodes/" + parentSite.getGuid() + "/children";
|
||||
String name = fileModel.getName();
|
||||
|
||||
HttpPost post = new HttpPost(reqUrl);
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("name", name);
|
||||
body.put("nodeType", "cm:content");
|
||||
|
||||
JSONObject jsonProperties = new JSONObject();
|
||||
jsonProperties.putAll(properties);
|
||||
body.put("properties", jsonProperties);
|
||||
|
||||
post.setEntity(client.setMessageBody(body));
|
||||
|
||||
// Send Request
|
||||
logger.info(String.format("POST: '%s'", reqUrl));
|
||||
HttpResponse response = client.execute(currentUser.getUsername(), currentUser.getPassword(), post);
|
||||
if (org.apache.http.HttpStatus.SC_CREATED != response.getStatusLine().getStatusCode())
|
||||
{
|
||||
throw new RuntimeException("Could not create file. Request response: " + client.getParameterFromJSON(response, "briefSummary", "error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.ContentModel;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
import org.alfresco.utility.report.log.Step;
|
||||
|
||||
/**
|
||||
* Tests to verify indexing of paths using Elasticsearch.
|
||||
*/
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert", "PMD.JUnit4TestShouldUseTestAnnotation"}) // these are testng tests and use searchQueryService.expectResultsFromQuery for assertion
|
||||
public class ElasticsearchPathIndexingTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
@Autowired
|
||||
private DataSite dataSite;
|
||||
@Autowired
|
||||
private DataContent dataContent;
|
||||
@Autowired
|
||||
protected SearchQueryService searchQueryService;
|
||||
|
||||
private org.alfresco.utility.model.UserModel testUser;
|
||||
|
||||
private org.alfresco.utility.model.SiteModel testSite;
|
||||
|
||||
private List<FolderModel> testFolders;
|
||||
|
||||
private String testFileName;
|
||||
private String filenameWhichIncludesWhitespace = "TestFile " + UUID.randomUUID() + ".txt";
|
||||
private String testFileNameWithWhitespace;
|
||||
|
||||
/**
|
||||
* Create a user and a private site containing some nested folders with a document in.
|
||||
*/
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
serverHealth.isServerReachable();
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
Step.STEP("Create a test user and private site containing three nested folders and a document.");
|
||||
testUser = dataUser.createRandomTestUser();
|
||||
testSite = dataSite.usingUser(testUser).createPrivateRandomSite();
|
||||
|
||||
testFolders = createNestedFolders(3);
|
||||
|
||||
testFileName = createDocument(testFolders.get(testFolders.size() - 1));
|
||||
|
||||
testFileNameWithWhitespace = createDocument(testFolders.get(testFolders.size() - 1), filenameWhichIncludesWhitespace);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSimple()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"//cm:" + testFileName + "\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testRelativePathQuery()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"//cm:" + testFileName + "\" ");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testRelativePathQueryWithoutPrefixes()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"//" + testFileName + "\" AND name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testWildcardQuery()
|
||||
{
|
||||
// The test file should be the only descendent of the last folder.
|
||||
SearchRequest query = req("PATH:\"//" + testSite.getId() + "//" + testFolders.get(testFolders.size() - 1).getName() + "/*\" AND name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFileName, testFileNameWithWhitespace);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testWildcardQueryWithNamespaces()
|
||||
{
|
||||
// The test file should be the only descendent of the last folder.
|
||||
SearchRequest query = req("PATH:\"//cm:" + testSite.getId() + "//cm:" + testFolders.get(testFolders.size() - 1).getName() + "/*\" AND name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFileName, testFileNameWithWhitespace);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testAbsolutePathQuery()
|
||||
{
|
||||
String folderPath = testFolders.stream().map(folder -> "cm:" + folder.getName()).collect(Collectors.joining("/"));
|
||||
SearchRequest query = req("PATH:\"/app:company_home/st:sites/cm:" + testSite.getId() + "/cm:documentLibrary/" + folderPath + "/cm:" + testFileName + "\" AND cm:name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testAbsolutePathQueryWithoutPrefixes()
|
||||
{
|
||||
String folderPath = testFolders.stream().map(folder -> folder.getName()).collect(Collectors.joining("/"));
|
||||
SearchRequest query = req("PATH:\"/company_home/sites/" + testSite.getId() + "/documentLibrary/" + folderPath + "/" + testFileName + "\" AND name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testRootNodes()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"/*\" AND name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, "categories", "Company Home");
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testRootNodesWithoutWildcard()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"/\"");
|
||||
searchQueryService.expectNodeTypesFromQuery(query, testUser, "sys:store_root");
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testPathNameMismatch()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"/*\" AND name:" + testFileName + " AND name:*");
|
||||
searchQueryService.expectNoResultsFromQuery(query, testUser);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testPathNameIntersect()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"//*\" AND name:" + testFileName + " AND name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testAllDescendentsOfFolder()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"//" + testFolders.get(0).getName() + "//*\" AND name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFileName, testFileNameWithWhitespace, testFolders.get(1).getName(), testFolders.get(2).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSearchAllDescendentsOfFolderQueryWithNamespaces()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"//cm:" + testFolders.get(0).getName() + "//*\" AND name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFileName, testFileNameWithWhitespace, testFolders.get(1).getName(), testFolders.get(2).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testWhereFolderIsAncestor()
|
||||
{
|
||||
SearchRequest query = req("ANCESTOR:\"" + testFolders.get(2).getNodeRef() + "\" AND name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFileName, testFileNameWithWhitespace);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testAncestorWithWorkspaceReference()
|
||||
{
|
||||
SearchRequest query = req("ANCESTOR:\"workspace://SpacesStore/" + testFolders.get(0).getNodeRef() + "\" AND name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFileName, testFileNameWithWhitespace, testFolders.get(1).getName(), testFolders.get(2).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testPrimaryParent()
|
||||
{
|
||||
SearchRequest query = req("PRIMARYPARENT:\"" + testFolders.get(1).getNodeRef() + "\" AND name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFolders.get(2).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testParent()
|
||||
{
|
||||
SearchRequest query = req("PARENT:\"" + testFolders.get(1).getNodeRef() + "\" AND name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFolders.get(2).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testAllFoldersInSite()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"/*/sites/" + testSite.getId() + "/*//*\" AND TYPE:\"cm:folder\" AND name:*");
|
||||
String[] folderNames = testFolders.stream().map(ContentModel::getName).toArray(String[]::new);
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, folderNames);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSearchAllFoldersInSiteQueryWithNamespaces()
|
||||
{
|
||||
SearchRequest query = req("PATH:\"/*/st:sites/cm:" + testSite.getId() + "/*//*\" AND TYPE:\"cm:folder\" AND name:*");
|
||||
String[] folderNames = testFolders.stream().map(ContentModel::getName).toArray(String[]::new);
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, folderNames);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH, enabled = false)
|
||||
public void testUpdatePath()
|
||||
{
|
||||
// disabled test: find a way to rename or move a folder on repository
|
||||
String folderPath = testFolders.stream().map(folder -> "cm:" + folder.getName()).collect(Collectors.joining("/"));
|
||||
SearchRequest query = req("PATH:\"/app:company_home/cm:" + testFileName + "\" AND cm:name:*");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a set of nested folders in the test site using the test user.
|
||||
*
|
||||
* @return The folder objects (containing the randomly generated names) in order of depth.
|
||||
*/
|
||||
private List<FolderModel> createNestedFolders(int maxDepth)
|
||||
{
|
||||
List<FolderModel> folders = new ArrayList<>();
|
||||
dataContent.usingSite(testSite);
|
||||
for (int depth = 0; depth < maxDepth; depth++)
|
||||
{
|
||||
String folderName = "TestFolder" + depth + "_" + UUID.randomUUID();
|
||||
FolderModel folderModel = new FolderModel(folderName);
|
||||
folders.add(folderModel);
|
||||
if (depth != 0)
|
||||
{
|
||||
dataContent.usingResource(folders.get(depth - 1));
|
||||
}
|
||||
dataContent.usingUser(testUser)
|
||||
.createFolder(folderModel);
|
||||
}
|
||||
return folders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a document in the given folder using the test user and a random filename.
|
||||
*
|
||||
* @param folderModel
|
||||
* The location to create the document.
|
||||
* @return The randomly generated name of the new document.
|
||||
*/
|
||||
private String createDocument(FolderModel folderModel)
|
||||
{
|
||||
return createDocument(folderModel, "TestFile" + UUID.randomUUID() + ".txt");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a document in the given folder using the test user and the given filename.
|
||||
*
|
||||
* @param folderModel
|
||||
* The location to create the document.
|
||||
* @param filename
|
||||
* the filename.
|
||||
* @return the passed filename.
|
||||
*/
|
||||
private String createDocument(FolderModel folderModel, String filename)
|
||||
{
|
||||
dataContent.usingUser(testUser)
|
||||
.usingResource(folderModel)
|
||||
.createContent(new org.alfresco.utility.model.FileModel(filename, org.alfresco.utility.model.FileType.TEXT_PLAIN, "content"));
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
+478
@@ -0,0 +1,478 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.search.ResponseHighlightModel;
|
||||
import org.alfresco.rest.search.RestRequestFieldsModel;
|
||||
import org.alfresco.rest.search.RestRequestHighlightModel;
|
||||
import org.alfresco.rest.search.SearchNodeModel;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
// These are TestNG tests and the assertions are hidden in searchQueryService.
|
||||
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"})
|
||||
public class ElasticsearchPlainHighlightingTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchPlainHighlightingTests.class);
|
||||
static final String FILE_A = "fileA.txt";
|
||||
static final String FILE_B = "fileB.txt";
|
||||
static final String FILE_C = "fileC.txt";
|
||||
static final String CONTENT_A = "The quick brown fox jumps over the lazy dog.";
|
||||
static final String CONTENT_B = """
|
||||
The lazy dog sleeps under the quick brown fox. The middle of the document is quite long:
|
||||
Lorem ipsum dolor sit amet. In veniam tempore hic provident sunt et distinctio velit et reprehenderit
|
||||
officiis id sapiente omnis et quisquam aliquam et porro perferendis. In sequi placeat ut quaerat voluptatem
|
||||
ea consequuntur impedit aut eaque enim aut atque rerum.
|
||||
The end of the document mentions the dog again!
|
||||
""";
|
||||
static final String CONTENT_C = """
|
||||
The rabbit made a ring in the center of the field. He said to the wolf, "Now, you dance around this ring, and sing just as I do."
|
||||
Rabbit made a larger ring for himself and danced around just beyond the wolf.
|
||||
The wolf thought that this was the finest dance he had ever seen.
|
||||
He and the rabbit danced faster and faster, and sang louder and louder.
|
||||
As the rabbit danced, he moved nearer and nearer to the edge of the field.
|
||||
The wolf was dancing so fast and singing so loud that he did not notice this.
|
||||
At last, Brother Rabbit reached the edge of the field; then he jumped into the blackberry bushes and ran away.
|
||||
The wolf tried to give chase, but he was so dizzy that he could not run. And the rabbit got away without having his ears cut off.
|
||||
""";
|
||||
|
||||
@Autowired
|
||||
ServerHealth serverHealth;
|
||||
@Autowired
|
||||
DataUser dataUser;
|
||||
@Autowired
|
||||
DataContent dataContent;
|
||||
@Autowired
|
||||
DataSite dataSite;
|
||||
@Autowired
|
||||
SearchQueryService searchQueryService;
|
||||
|
||||
UserModel user;
|
||||
SiteModel site;
|
||||
FileModel fileA;
|
||||
FileModel fileB;
|
||||
FileModel fileC;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
STEP("Create a test user and a private site.");
|
||||
user = dataUser.createRandomTestUser();
|
||||
site = dataSite.usingUser(user).createPrivateRandomSite();
|
||||
|
||||
STEP("Create two test files with highlightable content");
|
||||
FileModel fileModelA = new FileModel(FILE_A, FileType.TEXT_PLAIN, CONTENT_A);
|
||||
fileA = dataContent.usingUser(user).usingSite(site).createContent(fileModelA);
|
||||
FileModel fileModelB = new FileModel(FILE_B, FileType.TEXT_PLAIN, CONTENT_B);
|
||||
fileB = dataContent.usingUser(user).usingSite(site).createContent(fileModelB);
|
||||
FileModel fileModelC = new FileModel(FILE_C, FileType.TEXT_PLAIN, CONTENT_C);
|
||||
fileC = dataContent.usingUser(user).usingSite(site).createContent(fileModelC);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public void dataCleanup()
|
||||
{
|
||||
STEP("Remove test site and user");
|
||||
dataSite.usingAdmin().deleteSite(site);
|
||||
dataUser.usingAdmin().deleteUser(user);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsInContent()
|
||||
{
|
||||
STEP("Search for files mentioning 'dog'");
|
||||
String query = "cm:content:dog AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder().fields(List.of("cm:content")).build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_A, Map.of("cm:content", List.of("The quick brown fox jumps over the lazy <em>dog</em>.")),
|
||||
FILE_B, Map.of("cm:content", List.of("The lazy <em>dog</em> sleeps under the quick brown fox. The middle of the document is quite long:\nLorem",
|
||||
"\nea consequuntur impedit aut eaque enim aut atque rerum.\nThe end of the document mentions the <em>dog</em> again!\n"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsInTwoFields()
|
||||
{
|
||||
STEP("Search for files with 'file' in the name that mention 'middle'");
|
||||
String query = "cm:content:middle AND cm:name:file AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder().fields(List.of("cm:name", "cm:content")).build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_B, Map.of("cm:name", List.of("<em>file</em>B.txt"),
|
||||
"cm:content", List.of("The lazy dog sleeps under the quick brown fox. The <em>middle</em> of the document is quite long:\nLorem"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsWithCustomPrefixAndPostfix()
|
||||
{
|
||||
STEP("Search for files mentioning 'dog' with custom prefix and postfix");
|
||||
String query = "cm:content:dog AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.prefix("<b>")
|
||||
.postfix("</b>")
|
||||
.fields(List.of("cm:content"))
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_A, Map.of("cm:content", List.of("The quick brown fox jumps over the lazy <b>dog</b>.")),
|
||||
FILE_B, Map.of("cm:content", List.of("The lazy <b>dog</b> sleeps under the quick brown fox. The middle of the document is quite long:\nLorem",
|
||||
"\nea consequuntur impedit aut eaque enim aut atque rerum.\nThe end of the document mentions the <b>dog</b> again!\n"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsWithBlankPrefixAndPostfix()
|
||||
{
|
||||
STEP("Search for files mentioning 'middle' with blank mark as custom prefix and postfix");
|
||||
String query = "cm:content:middle AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.prefix(" ")
|
||||
.postfix(" ")
|
||||
.fields(List.of("cm:content"))
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_B, Map.of("cm:content", List.of("The lazy dog sleeps under the quick brown fox. The middle of the document is quite long:\nLorem"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsWithGeneralAndFieldSpecificPrefixesAndPostfixes()
|
||||
{
|
||||
STEP("Search for files with 'file' in the name that mention 'dog' with general and field specific prefixes or postfixes");
|
||||
String query = "cm:content:dog AND cm:name:file AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.prefix("¿")
|
||||
.postfix("?")
|
||||
.fields(
|
||||
RestRequestFieldsModel.of("cm:name"),
|
||||
RestRequestFieldsModel.of("cm:content", "(", ")"))
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_A, Map.of("cm:name", List.of("¿file?A.txt"), "cm:content", List.of("The quick brown fox jumps over the lazy (dog).")),
|
||||
FILE_B, Map.of("cm:name", List.of("¿file?B.txt"), "cm:content", List.of("The lazy (dog) sleeps under the quick brown fox. The middle of the document is quite long:\nLorem",
|
||||
"\nea consequuntur impedit aut eaque enim aut atque rerum.\nThe end of the document mentions the (dog) again!\n"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsWithOneFieldSpecificPrefixAndPostfix()
|
||||
{
|
||||
STEP("Search for files with 'file' in the name that mention 'dog' with one field specific prefix or postfix");
|
||||
String query = "cm:content:dog AND cm:name:file AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.fields(
|
||||
RestRequestFieldsModel.of("cm:name"),
|
||||
RestRequestFieldsModel.of("cm:content", "(", ")"))
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_A, Map.of("cm:name", List.of("<em>file</em>A.txt"), "cm:content", List.of("The quick brown fox jumps over the lazy (dog).")),
|
||||
FILE_B, Map.of("cm:name", List.of("<em>file</em>B.txt"), "cm:content", List.of("The lazy (dog) sleeps under the quick brown fox. The middle of the document is quite long:\nLorem",
|
||||
"\nea consequuntur impedit aut eaque enim aut atque rerum.\nThe end of the document mentions the (dog) again!\n"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsInTwoFieldsWithPrefixAndWithoutPostfix()
|
||||
{
|
||||
STEP("Search for files with 'file' in the name that mention 'middle' with custom prefix");
|
||||
String query = "cm:content:middle AND cm:name:file AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.fields(List.of("cm:name", "cm:content"))
|
||||
.prefix("(")
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_B, Map.of("cm:name", List.of("(file</em>B.txt"),
|
||||
"cm:content", List.of("The lazy dog sleeps under the quick brown fox. The (middle</em> of the document is quite long:\nLorem"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsInTwoFieldsWithoutPrefixAndWithPostfix()
|
||||
{
|
||||
STEP("Search for files with 'file' in the name that mention 'middle' with custom postfix");
|
||||
String query = "cm:content:middle AND cm:name:file AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.fields(List.of("cm:name", "cm:content"))
|
||||
.postfix(")")
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_B, Map.of("cm:name", List.of("<em>file)B.txt"),
|
||||
"cm:content", List.of("The lazy dog sleeps under the quick brown fox. The <em>middle) of the document is quite long:\nLorem"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsInContentWithGeneralSnippetCount()
|
||||
{
|
||||
STEP("Search for files mentioning 'rabbit' and expect 2 snippets in result");
|
||||
String query = "cm:content:rabbit AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.fields(RestRequestFieldsModel.of("cm:content"))
|
||||
.snippetCount(2)
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_C, Map.of("cm:content", List.of("The <em>rabbit</em> made a ring in the center of the field. He said to the wolf, \"Now, you dance around",
|
||||
" this ring, and sing just as I do.\"\n<em>Rabbit</em> made a larger ring for himself and danced around just beyond"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsInContentWithFieldSpecificSnippetCount()
|
||||
{
|
||||
STEP("Search for files mentioning 'rabbit' and expect 3 snippets in result");
|
||||
String query = "cm:content:rabbit AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.fields(RestRequestFieldsModel.builder().field("cm:content").snippetCount(3).build())
|
||||
.snippetCount(1)
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_C, Map.of("cm:content", List.of("The <em>rabbit</em> made a ring in the center of the field. He said to the wolf, \"Now, you dance around",
|
||||
" this ring, and sing just as I do.\"\n<em>Rabbit</em> made a larger ring for himself and danced around just beyond",
|
||||
" the wolf.\nThe wolf thought that this was the finest dance he had ever seen.\nHe and the <em>rabbit</em> danced"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsInContentWithZeroSnippetCount()
|
||||
{
|
||||
STEP("Search for files mentioning 'rabbit' and expect default 5 snippets in result");
|
||||
String query = "cm:content:rabbit AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.fields(RestRequestFieldsModel.of("cm:content"))
|
||||
.snippetCount(0)
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_C, Map.of("cm:content", List.of("The <em>rabbit</em> made a ring in the center of the field. He said to the wolf, \"Now, you dance around",
|
||||
" this ring, and sing just as I do.\"\n<em>Rabbit</em> made a larger ring for himself and danced around just beyond",
|
||||
" the wolf.\nThe wolf thought that this was the finest dance he had ever seen.\nHe and the <em>rabbit</em> danced",
|
||||
" faster and faster, and sang louder and louder.\nAs the <em>rabbit</em> danced, he moved nearer and nearer",
|
||||
" this.\nAt last, Brother <em>Rabbit</em> reached the edge of the field; then he jumped into the blackberry bushes"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsInContentWithNegativeSnippetCount()
|
||||
{
|
||||
STEP("Search for files mentioning 'rabbit' and expect default 5 snippets in result");
|
||||
String query = "cm:content:rabbit AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.fields(RestRequestFieldsModel.of("cm:content"))
|
||||
.snippetCount(-1)
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_C, Map.of("cm:content", List.of("The <em>rabbit</em> made a ring in the center of the field. He said to the wolf, \"Now, you dance around",
|
||||
" this ring, and sing just as I do.\"\n<em>Rabbit</em> made a larger ring for himself and danced around just beyond",
|
||||
" the wolf.\nThe wolf thought that this was the finest dance he had ever seen.\nHe and the <em>rabbit</em> danced",
|
||||
" faster and faster, and sang louder and louder.\nAs the <em>rabbit</em> danced, he moved nearer and nearer",
|
||||
" this.\nAt last, Brother <em>Rabbit</em> reached the edge of the field; then he jumped into the blackberry bushes"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsInContentWithGeneralFragmentSize()
|
||||
{
|
||||
STEP("Search for files mentioning 'dog' and expect snippets about 10 characters long");
|
||||
String query = "cm:content:dog AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.fields(RestRequestFieldsModel.of("cm:content"))
|
||||
.fragmentSize(10)
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_A, Map.of("cm:content", List.of(" over the lazy <em>dog</em>.")),
|
||||
FILE_B, Map.of("cm:content", List.of(" <em>dog</em> sleeps", " the <em>dog</em> again!\n"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsInContentWithFieldSpecificFragmentSize()
|
||||
{
|
||||
STEP("Search for files mentioning 'dog' and expect snippets about 15 characters long");
|
||||
String query = "cm:content:dog AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.fields(RestRequestFieldsModel.builder().field("cm:content").fragmentSize(15).build())
|
||||
.fragmentSize(10)
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_A, Map.of("cm:content", List.of(" over the lazy <em>dog</em>.")),
|
||||
FILE_B, Map.of("cm:content", List.of("The lazy <em>dog</em>", " the <em>dog</em> again!\n"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsInContentWithZeroFragmentSize()
|
||||
{
|
||||
STEP("Search for files mentioning 'dog' and expect snippets having default characters length");
|
||||
String query = "cm:content:dog AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.fields(RestRequestFieldsModel.of("cm:content"))
|
||||
.fragmentSize(0)
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_A, Map.of("cm:content", List.of("The quick brown fox jumps over the lazy <em>dog</em>.")),
|
||||
FILE_B, Map.of("cm:content", List.of("The lazy <em>dog</em> sleeps under the quick brown fox. The middle of the document is quite long:\nLorem",
|
||||
"\nea consequuntur impedit aut eaque enim aut atque rerum.\nThe end of the document mentions the <em>dog</em> again!\n"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testHighlightsInContentWithNegativeFragmentSize()
|
||||
{
|
||||
STEP("Search for files mentioning 'dog' and expect snippets having default characters length");
|
||||
String query = "cm:content:dog AND SITE:" + site.getId();
|
||||
SearchRequest searchRequest = req("afts", query);
|
||||
RestRequestHighlightModel highlightModel = RestRequestHighlightModel.builder()
|
||||
.fields(RestRequestFieldsModel.of("cm:content"))
|
||||
.fragmentSize(-1)
|
||||
.build();
|
||||
searchRequest.setHighlight(highlightModel);
|
||||
// Configure the expected highlights for each document.
|
||||
Predicate<SearchNodeModel> assertionMethod = highlightAssert(
|
||||
Map.of(FILE_A, Map.of("cm:content", List.of("The quick brown fox jumps over the lazy <em>dog</em>.")),
|
||||
FILE_B, Map.of("cm:content", List.of("The lazy <em>dog</em> sleeps under the quick brown fox. The middle of the document is quite long:\nLorem",
|
||||
"\nea consequuntur impedit aut eaque enim aut atque rerum.\nThe end of the document mentions the <em>dog</em> again!\n"))));
|
||||
searchQueryService.expectAllResultsFromQuery(searchRequest, user, assertionMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a predicate that returns true if the highlights for a received document match the given expectation.
|
||||
*
|
||||
* @param allExpectedHighlights
|
||||
* The expected highlights for all documents keyed by document name and then field name.
|
||||
* @return The predicate.
|
||||
*/
|
||||
private Predicate<SearchNodeModel> highlightAssert(Map<String, Map<String, List<String>>> allExpectedHighlights)
|
||||
{
|
||||
return document -> {
|
||||
if (!allExpectedHighlights.containsKey(document.getName()))
|
||||
{
|
||||
LOGGER.error("Unexpected entry in results: {}", document.getName());
|
||||
return false;
|
||||
}
|
||||
Map<String, List<String>> expectedHighlights = allExpectedHighlights.get(document.getName());
|
||||
List<ResponseHighlightModel> actualHighlights = document.getSearch().getHighlight();
|
||||
Set<String> actualFields = actualHighlights.stream()
|
||||
.map(ResponseHighlightModel::getField)
|
||||
.collect(toSet());
|
||||
if (!actualFields.equals(expectedHighlights.keySet()))
|
||||
{
|
||||
LOGGER.error("Unexpected field set for {}: {}", document.getName(), actualFields);
|
||||
return false;
|
||||
}
|
||||
Set<ResponseHighlightModel> expectedHighlightResponse = new HashSet<>();
|
||||
for (String expectedField : expectedHighlights.keySet())
|
||||
{
|
||||
ResponseHighlightModel expectedHighlight = new ResponseHighlightModel();
|
||||
List<String> expectedSnippets = expectedHighlights.get(expectedField);
|
||||
expectedHighlight.setField(expectedField);
|
||||
expectedHighlight.setSnippets(expectedSnippets);
|
||||
expectedHighlightResponse.add(expectedHighlight);
|
||||
}
|
||||
if (!new HashSet<>(actualHighlights).equals(expectedHighlightResponse))
|
||||
{
|
||||
LOGGER.error("Unexpected highlights for {}, {}", document.getName(), actualHighlights);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.ContentModel;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
public class ElasticsearchProximitySearchTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
@Autowired
|
||||
ServerHealth serverHealth;
|
||||
|
||||
@Autowired
|
||||
DataUser dataUser;
|
||||
|
||||
@Autowired
|
||||
DataContent dataContent;
|
||||
|
||||
@Autowired
|
||||
SearchQueryService searchQueryService;
|
||||
|
||||
private UserModel testUser;
|
||||
private String contentA;
|
||||
private String contentB;
|
||||
private String contentC;
|
||||
private String contentD;
|
||||
private String nameA;
|
||||
private String nameB;
|
||||
private String nameC;
|
||||
private String nameD;
|
||||
private String file_NameA_ContentABCD;
|
||||
private String file_NameAB_ContentABC;
|
||||
private String file_NameABC_ContentAB;
|
||||
private String file_NameABCD_ContentA;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
nameA = uniqueString();
|
||||
nameB = uniqueString();
|
||||
nameC = uniqueString();
|
||||
nameD = uniqueString();
|
||||
|
||||
contentA = uniqueString();
|
||||
contentB = uniqueString();
|
||||
contentC = uniqueString();
|
||||
contentD = uniqueString();
|
||||
|
||||
file_NameA_ContentABCD = createContent(join(nameA), join(contentA, contentB, contentC, contentD));
|
||||
file_NameAB_ContentABC = createContent(join(nameA, nameB), join(contentA, contentB, contentC));
|
||||
file_NameABC_ContentAB = createContent(join(nameA, nameB, nameC), join(contentA, contentB));
|
||||
file_NameABCD_ContentA = createContent(join(nameA, nameB, nameC, nameD), join(contentA));
|
||||
|
||||
testUser = dataUser.createRandomTestUser();
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH, TestGroup.TAGS, TestGroup.REGRESSION})
|
||||
public void testProximitySearchUsingAFTSSyntax()
|
||||
{
|
||||
final String AFTS = "%s *%s %s";
|
||||
|
||||
assertAFTS(format(AFTS, contentA, "(0)", contentB), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
|
||||
assertAFTS(format(AFTS, contentA, "(1)", contentB), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
|
||||
assertAFTS(format(AFTS, contentA, "(2)", contentB), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
|
||||
assertAFTS(format(AFTS, contentA, "(3)", contentB), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
|
||||
assertAFTS(format(AFTS, contentA, "", contentB), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
|
||||
|
||||
assertAFTS(format(AFTS, contentA, "(0)", contentC));
|
||||
assertAFTS(format(AFTS, contentA, "(1)", contentC), file_NameA_ContentABCD, file_NameAB_ContentABC);
|
||||
assertAFTS(format(AFTS, contentA, "(2)", contentC), file_NameA_ContentABCD, file_NameAB_ContentABC);
|
||||
assertAFTS(format(AFTS, contentA, "(3)", contentC), file_NameA_ContentABCD, file_NameAB_ContentABC);
|
||||
assertAFTS(format(AFTS, contentA, "", contentC), file_NameA_ContentABCD, file_NameAB_ContentABC);
|
||||
|
||||
assertAFTS(format(AFTS, contentA, "(0)", contentD));
|
||||
assertAFTS(format(AFTS, contentA, "(1)", contentD));
|
||||
assertAFTS(format(AFTS, contentA, "(2)", contentD), file_NameA_ContentABCD);
|
||||
assertAFTS(format(AFTS, contentA, "(3)", contentD), file_NameA_ContentABCD);
|
||||
assertAFTS(format(AFTS, contentA, "", contentD), file_NameA_ContentABCD);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH, TestGroup.TAGS, TestGroup.REGRESSION})
|
||||
public void testProximitySearchUsingAFTSSyntaxForSpecificProperty()
|
||||
{
|
||||
final String AFTS = "cm:name:(%s *%s %s)";
|
||||
|
||||
assertAFTS(format(AFTS, nameA, "(0)", nameB), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
assertAFTS(format(AFTS, nameA, "(1)", nameB), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
assertAFTS(format(AFTS, nameA, "(2)", nameB), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
assertAFTS(format(AFTS, nameA, "(3)", nameB), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
assertAFTS(format(AFTS, nameA, "", nameB), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
|
||||
assertAFTS(format(AFTS, nameA, "(0)", nameC));
|
||||
assertAFTS(format(AFTS, nameA, "(1)", nameC), file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
assertAFTS(format(AFTS, nameA, "(2)", nameC), file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
assertAFTS(format(AFTS, nameA, "(3)", nameC), file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
assertAFTS(format(AFTS, nameA, "", nameC), file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
|
||||
assertAFTS(format(AFTS, nameA, "(0)", nameD));
|
||||
assertAFTS(format(AFTS, nameA, "(1)", nameD));
|
||||
assertAFTS(format(AFTS, nameA, "(2)", nameD), file_NameABCD_ContentA);
|
||||
assertAFTS(format(AFTS, nameA, "(3)", nameD), file_NameABCD_ContentA);
|
||||
assertAFTS(format(AFTS, nameA, "", nameD), file_NameABCD_ContentA);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH, TestGroup.TAGS, TestGroup.REGRESSION})
|
||||
public void testProximitySearchUsingLuceneSyntax()
|
||||
{
|
||||
final String lucene = "\"%s %s\"~%d";
|
||||
|
||||
assertLucene(format(lucene, contentA, contentB, 0), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
|
||||
assertLucene(format(lucene, contentA, contentB, 1), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
|
||||
assertLucene(format(lucene, contentA, contentB, 2), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
|
||||
assertLucene(format(lucene, contentA, contentB, 3), file_NameA_ContentABCD, file_NameAB_ContentABC, file_NameABC_ContentAB);
|
||||
|
||||
assertLucene(format(lucene, contentA, contentC, 0));
|
||||
assertLucene(format(lucene, contentA, contentC, 1), file_NameA_ContentABCD, file_NameAB_ContentABC);
|
||||
assertLucene(format(lucene, contentA, contentC, 2), file_NameA_ContentABCD, file_NameAB_ContentABC);
|
||||
assertLucene(format(lucene, contentA, contentC, 3), file_NameA_ContentABCD, file_NameAB_ContentABC);
|
||||
|
||||
assertLucene(format(lucene, contentA, contentD, 0));
|
||||
assertLucene(format(lucene, contentA, contentD, 1));
|
||||
assertLucene(format(lucene, contentA, contentD, 2), file_NameA_ContentABCD);
|
||||
assertLucene(format(lucene, contentA, contentD, 3), file_NameA_ContentABCD);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH, TestGroup.TAGS, TestGroup.REGRESSION})
|
||||
public void testProximitySearchUsingLuceneSyntaxForSpecificProperty()
|
||||
{
|
||||
final String lucene = "@cm\\:name:\"%s %s\"~%d";
|
||||
|
||||
assertLucene(format(lucene, nameA, nameB, 0), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
assertLucene(format(lucene, nameA, nameB, 1), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
assertLucene(format(lucene, nameA, nameB, 2), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
assertLucene(format(lucene, nameA, nameB, 3), file_NameAB_ContentABC, file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
|
||||
assertLucene(format(lucene, nameA, nameC, 0));
|
||||
assertLucene(format(lucene, nameA, nameC, 1), file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
assertLucene(format(lucene, nameA, nameC, 2), file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
assertLucene(format(lucene, nameA, nameC, 3), file_NameABC_ContentAB, file_NameABCD_ContentA);
|
||||
|
||||
assertLucene(format(lucene, nameA, nameD, 0));
|
||||
assertLucene(format(lucene, nameA, nameD, 1));
|
||||
assertLucene(format(lucene, nameA, nameD, 2), file_NameABCD_ContentA);
|
||||
assertLucene(format(lucene, nameA, nameD, 3), file_NameABCD_ContentA);
|
||||
}
|
||||
|
||||
private void assertLucene(String query, String... expected)
|
||||
{
|
||||
assertQueryResult("lucene", query, expected);
|
||||
}
|
||||
|
||||
private void assertAFTS(String query, String... expected)
|
||||
{
|
||||
assertQueryResult("afts", query, expected);
|
||||
}
|
||||
|
||||
private void assertQueryResult(String language, String query, String... expected)
|
||||
{
|
||||
final SearchRequest searchRequest = req(language, query);
|
||||
if (expected.length == 0)
|
||||
{
|
||||
searchQueryService.expectNoResultsFromQuery(searchRequest, testUser);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchQueryService.expectNodeRefsFromQuery(searchRequest, testUser, expected);
|
||||
}
|
||||
}
|
||||
|
||||
private String createContent(String filename, String content)
|
||||
{
|
||||
return dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(contentRoot())
|
||||
.createContent(new FileModel(filename, FileType.TEXT_PLAIN, content))
|
||||
.getNodeRef();
|
||||
}
|
||||
|
||||
private static ContentModel contentRoot()
|
||||
{
|
||||
final ContentModel root = new ContentModel("-root-");
|
||||
root.setNodeRef(root.getName());
|
||||
return root;
|
||||
}
|
||||
|
||||
private String join(String... parts)
|
||||
{
|
||||
return String.join(" ", parts);
|
||||
}
|
||||
|
||||
private static String uniqueString()
|
||||
{
|
||||
final UUID unique = UUID.randomUUID();
|
||||
final StringBuilder result = new StringBuilder(Long.SIZE / 2);
|
||||
for (long l : List.of(unique.getMostSignificantBits(), unique.getLeastSignificantBits()))
|
||||
{
|
||||
for (int i = Long.SIZE - 4; i >= 0; i -= 4)
|
||||
{
|
||||
int ch = 'a' + (byte) ((((l & (0xFL << i)) >> i)) & 0xFL);
|
||||
result.append(Character.toChars(ch));
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
+522
@@ -0,0 +1,522 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.chemistry.opencmis.client.api.CmisObject;
|
||||
import org.apache.chemistry.opencmis.client.api.Document;
|
||||
import org.apache.chemistry.opencmis.client.api.Folder;
|
||||
import org.apache.chemistry.opencmis.client.api.Session;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.dataprep.AlfrescoHttpClientFactory;
|
||||
import org.alfresco.dataprep.CMISUtil;
|
||||
import org.alfresco.dataprep.ContentActions;
|
||||
import org.alfresco.dataprep.SiteService.Visibility;
|
||||
import org.alfresco.rest.core.RestWrapper;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.ContentModel;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
import org.alfresco.utility.report.log.Step;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
public class ElasticsearchSiteIndexingTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
private static final Iterable<String> LANGUAGES_TO_CHECK = List.of("afts", "lucene");
|
||||
private static final FileModel DOCUMENT_LIBRARY = new FileModel("documentLibrary");
|
||||
private static final String FILENAME_PREFIX = "EsSiteTest";
|
||||
private static final String FILE_CONTENT_CONDITION = " TEXT:" + FILENAME_PREFIX + "*";
|
||||
private static final String SAMPLE_SITE_ID = "swsdp";
|
||||
private static final String ALL_SITES = "_ALL_SITES_ ";
|
||||
private static final String EVERYTHING = "_EVERYTHING_ ";
|
||||
|
||||
@Autowired
|
||||
ServerHealth serverHealth;
|
||||
|
||||
@Autowired
|
||||
DataUser dataUser;
|
||||
|
||||
@Autowired
|
||||
DataContent dataContent;
|
||||
|
||||
@Autowired
|
||||
public DataSite dataSite;
|
||||
|
||||
@Autowired
|
||||
private AlfrescoHttpClientFactory alfrescoHttpClientFactory;
|
||||
|
||||
@Autowired
|
||||
SearchQueryService searchQueryService;
|
||||
|
||||
@Autowired
|
||||
protected RestWrapper restClient;
|
||||
|
||||
private UserModel testUser;
|
||||
private UserModel siteCreator;
|
||||
private FolderModel testFolder;
|
||||
private SiteModel testSite1;
|
||||
private SiteModel testSite2;
|
||||
private FileModel fileNotInSite;
|
||||
private FileModel file1;
|
||||
private FileModel file2;
|
||||
private FileModel file3;
|
||||
private FileModel file4;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
Step.STEP("Create test users and a folder.");
|
||||
testUser = dataUser.createRandomTestUser();
|
||||
siteCreator = dataUser.createRandomTestUser();
|
||||
testFolder = dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(contentRoot())
|
||||
.createFolder(new FolderModel(unique("FOLDER")));
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH, TestGroup.SITES, TestGroup.REGRESSION})
|
||||
public void testSiteUseCasesForCreateModifyDeleteSite()
|
||||
{
|
||||
// Remove the automatically created Sample Site
|
||||
deleteSite(SAMPLE_SITE_ID);
|
||||
|
||||
// Sometimes this test may fail, so if previous data exists it must be deleted before the next run
|
||||
Stream.of(fileNotInSite, file1, file2, file3, file4)
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(this::deleteFile);
|
||||
Stream.of(testSite1, testSite2)
|
||||
.filter(Objects::nonNull)
|
||||
.map(SiteModel::getId)
|
||||
.forEach(this::deleteSite);
|
||||
|
||||
Step.STEP("Site creation use cases");
|
||||
|
||||
// Check there are no files within or without a site, that have the given filename prefix
|
||||
assertSiteQueryResult(ALL_SITES, List.of());
|
||||
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of());
|
||||
|
||||
// Create a file with the given filename prefix, outside any site
|
||||
fileNotInSite = new FileModel(unique(FILENAME_PREFIX) + ".txt", FileType.TEXT_PLAIN, "Content for fileNotInSite");
|
||||
fileNotInSite = dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(testFolder)
|
||||
.createContent(fileNotInSite);
|
||||
|
||||
// Search with condition of filename prefix - we should see the file using EVERYTHING but not from ALL_SITES
|
||||
assertSiteQueryResult(ALL_SITES, "AND", FILE_CONTENT_CONDITION, List.of());
|
||||
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite));
|
||||
|
||||
// No sites should exist - expect no results
|
||||
assertSiteQueryResult(ALL_SITES, List.of());
|
||||
assertSiteQueryResult(unique("NoSuchSite"), List.of());
|
||||
|
||||
// Create one empty public site - expect no results other than document library
|
||||
testSite1 = createPublicSite();
|
||||
assertSiteQueryResult(testSite1.getId(), List.of(DOCUMENT_LIBRARY));
|
||||
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY));
|
||||
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite));
|
||||
|
||||
// Create a file in public site - expect one file plus the document library.
|
||||
file1 = createContentInSite(testSite1, FILENAME_PREFIX + "test1");
|
||||
assertSiteQueryResult(testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1));
|
||||
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY, file1));
|
||||
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1));
|
||||
|
||||
// Create another file in public site - expect two files plus the document library.
|
||||
file2 = createContentInSite(testSite1, FILENAME_PREFIX + "test2");
|
||||
assertSiteQueryResult(testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
|
||||
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2));
|
||||
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2));
|
||||
|
||||
// Create a second public site, empty - expect no results other than document library
|
||||
testSite2 = createPublicSite();
|
||||
assertSiteQueryResult(testSite2.getId(), List.of(DOCUMENT_LIBRARY));
|
||||
assertSiteQueryResult(testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
|
||||
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2));
|
||||
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2));
|
||||
|
||||
// Create a file in second public site - expect one file plus the document library.
|
||||
file3 = createContentInSite(testSite2, FILENAME_PREFIX + "test3");
|
||||
assertSiteQueryResult(testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3));
|
||||
assertSiteQueryResult(testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
|
||||
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2, file3));
|
||||
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2, file3));
|
||||
|
||||
// Create another file in second public site - expect two files plus the document library.
|
||||
file4 = createContentInSite(testSite2, FILENAME_PREFIX + "test4");
|
||||
assertSiteQueryResult(testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
|
||||
assertSiteQueryResult(testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
|
||||
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2, file3, file4));
|
||||
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2, file3, file4));
|
||||
|
||||
// Test disjunction and conjunction using SITE: and SITE:
|
||||
assertSiteQueryResult(testSite1.getId(), "OR", " SITE:" + testSite2.getId(), List.of(DOCUMENT_LIBRARY, file1, file2, file3, file4));
|
||||
assertSiteQueryResult(testSite1.getId(), "OR", " SITE:" + unique("NoSuchSite"), List.of(DOCUMENT_LIBRARY, file1, file2));
|
||||
assertSiteQueryResult(testSite1.getId(), "AND", " SITE:" + unique("NoSuchSite"), List.of());
|
||||
|
||||
// Test conjunction using SITE: and TEXT:
|
||||
assertSiteQueryResult(testSite1.getId(), "AND", " TEXT:" + FILENAME_PREFIX + "test2*", List.of(file2));
|
||||
assertSiteQueryResult(testSite1.getId(), "AND", " TEXT:" + FILENAME_PREFIX + "testX*", List.of());
|
||||
assertSiteQueryResult(EVERYTHING, "AND", " TEXT:" + FILENAME_PREFIX + "test2*", List.of(file2));
|
||||
assertSiteQueryResult(EVERYTHING, "AND", " TEXT:" + FILENAME_PREFIX + "testX*", List.of());
|
||||
assertSiteQueryResult(ALL_SITES, "AND", " TEXT:" + FILENAME_PREFIX + "test2*", List.of(file2));
|
||||
assertSiteQueryResult(ALL_SITES, "AND", " TEXT:" + FILENAME_PREFIX + "testX*", List.of());
|
||||
|
||||
// Test modify site
|
||||
Step.STEP("Site modification use cases");
|
||||
|
||||
// Verify site creating user can see all files in both public sites
|
||||
assertSiteQueryResult(siteCreator, testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
|
||||
assertSiteQueryResult(siteCreator, testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
|
||||
assertSiteQueryResult(siteCreator, ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2, file3, file4));
|
||||
assertSiteQueryResult(siteCreator, EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2, file3, file4));
|
||||
|
||||
// Verify user who is not a member of any sites can see all files in both public sites
|
||||
UserModel publicUser = dataUser.createRandomTestUser();
|
||||
assertSiteQueryResult(publicUser, testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
|
||||
assertSiteQueryResult(publicUser, testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
|
||||
assertSiteQueryResult(publicUser, ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2, file3, file4));
|
||||
assertSiteQueryResult(publicUser, EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2, file3, file4));
|
||||
|
||||
// Change first site's visibility to private - verify site creator can still see all files in both sites
|
||||
dataSite.usingUser(siteCreator).updateSiteVisibility(testSite1, Visibility.PRIVATE);
|
||||
assertSiteQueryResult(siteCreator, testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
|
||||
assertSiteQueryResult(siteCreator, testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
|
||||
assertSiteQueryResult(siteCreator, ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2, file3, file4));
|
||||
assertSiteQueryResult(siteCreator, EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2, file3, file4));
|
||||
|
||||
// Verify user who is not a member of any sites can see files in public site but not see files in private site
|
||||
assertSiteQueryResult(publicUser, testSite1.getId(), List.of());
|
||||
assertSiteQueryResult(publicUser, testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
|
||||
assertSiteQueryResult(publicUser, ALL_SITES, List.of(DOCUMENT_LIBRARY, file3, file4));
|
||||
assertSiteQueryResult(publicUser, EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file3, file4));
|
||||
|
||||
// Change site visibility back to public - user who is not a member of any sites can see files in both sites again
|
||||
dataSite.usingUser(siteCreator).updateSiteVisibility(testSite1, Visibility.PUBLIC);
|
||||
assertSiteQueryResult(publicUser, testSite1.getId(), List.of(DOCUMENT_LIBRARY, file1, file2));
|
||||
assertSiteQueryResult(publicUser, testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
|
||||
assertSiteQueryResult(publicUser, ALL_SITES, List.of(DOCUMENT_LIBRARY, file1, file2, file3, file4));
|
||||
assertSiteQueryResult(publicUser, EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file1, file2, file3, file4));
|
||||
|
||||
// Test delete site
|
||||
Step.STEP("Site deletion use cases");
|
||||
|
||||
// Delete one site - expect no results for that site
|
||||
deleteSite(testSite1.getId());
|
||||
assertSiteQueryResult(testSite1.getId(), List.of());
|
||||
assertSiteQueryResult(testSite2.getId(), List.of(DOCUMENT_LIBRARY, file3, file4));
|
||||
assertSiteQueryResult(ALL_SITES, List.of(DOCUMENT_LIBRARY, file3, file4));
|
||||
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite, file3, file4));
|
||||
|
||||
// Delete remaining site - expect no results
|
||||
deleteSite(testSite2.getId());
|
||||
assertSiteQueryResult(testSite2.getId(), List.of());
|
||||
assertSiteQueryResult(testSite1.getId(), List.of());
|
||||
assertSiteQueryResult(ALL_SITES, List.of());
|
||||
assertSiteQueryResult(EVERYTHING, "AND", FILE_CONTENT_CONDITION, List.of(fileNotInSite));
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH, TestGroup.SITES, TestGroup.REGRESSION})
|
||||
public void manipulatingFilesAndContentBetweenSites()
|
||||
{
|
||||
Step.STEP("Moving files between sites and modifying content use cases");
|
||||
|
||||
SiteModel publicSite1 = createPublicSite(siteCreator);
|
||||
SiteModel publicSite2 = createPublicSite(siteCreator);
|
||||
FileModel file5 = createContentInSite(publicSite1, "file5");
|
||||
assertSiteQueryResult(publicSite1.getId(), List.of(DOCUMENT_LIBRARY, file5));
|
||||
|
||||
// Moving a file between sites.
|
||||
moveFile(file5, publicSite1, publicSite2);
|
||||
assertSiteQueryResult(publicSite1.getId(), List.of(DOCUMENT_LIBRARY));
|
||||
assertSiteQueryResult(publicSite2.getId(), List.of(DOCUMENT_LIBRARY, file5));
|
||||
|
||||
// Moving a file out of a site.
|
||||
moveFileOutsideOfSite(file5, publicSite2, testFolder);
|
||||
assertSiteQueryResult(publicSite2.getId(), List.of(DOCUMENT_LIBRARY));
|
||||
|
||||
// Moving a file to the site
|
||||
FileModel file6 = dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(contentRoot())
|
||||
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
|
||||
FileModel file7 = createContentInSite(publicSite1, "file7"); // Ensuring that DocumentLibrary exists in the publicSite1
|
||||
assertSiteQueryResult(publicSite1.getId(), List.of(DOCUMENT_LIBRARY, file7));
|
||||
moveFileToTheSite(file6, publicSite1);
|
||||
assertSiteQueryResult(publicSite1.getId(), List.of(DOCUMENT_LIBRARY, file6, file7));
|
||||
|
||||
// Removing file.
|
||||
FileModel file8 = createContentInSite(publicSite2, "file8");
|
||||
assertSiteQueryResult(publicSite2.getId(), List.of(DOCUMENT_LIBRARY, file8));
|
||||
deleteFile(file8);
|
||||
assertSiteQueryResult(publicSite2.getId(), List.of(DOCUMENT_LIBRARY));
|
||||
|
||||
// Document modification.
|
||||
FileModel file9 = createContentInSite(publicSite2, "file9", "Initial Content.");
|
||||
assertContentInGivenFileUnderSiteQueryResult(publicSite2.getId(), "initial", List.of(file9));
|
||||
modifyDocument(file9, "Modified Content.");
|
||||
assertContentInGivenFileUnderSiteQueryResult(publicSite2.getId(), "modified", List.of(file9));
|
||||
|
||||
// Cleanup.
|
||||
deleteFiles(file6, file7, file9);
|
||||
dataContent.usingAdmin().deleteSite(publicSite1);
|
||||
dataContent.usingAdmin().deleteSite(publicSite2);
|
||||
}
|
||||
|
||||
private void deleteFiles(FileModel... fileModels)
|
||||
{
|
||||
for (FileModel fileModel : fileModels)
|
||||
{
|
||||
deleteFile(fileModel);
|
||||
}
|
||||
}
|
||||
|
||||
private void moveFileToTheSite(FileModel file, SiteModel targetSite)
|
||||
{
|
||||
Session session = dataContent.getContentActions().getCMISSession(alfrescoHttpClientFactory.getAdminUser(), alfrescoHttpClientFactory.getAdminPassword());
|
||||
ContentActions actions = dataContent.usingAdmin().getContentActions();
|
||||
CmisObject objFrom = actions.getCmisObject(session, file.getName());
|
||||
CmisObject objTarget = session.getObjectByPath("/Sites/" + targetSite.getId() + "/documentLibrary");
|
||||
|
||||
List parents;
|
||||
CmisObject parent;
|
||||
if (objFrom instanceof Document)
|
||||
{
|
||||
Document d = (Document) objFrom;
|
||||
parents = d.getParents();
|
||||
parent = session.getObject(((Folder) parents.get(0)).getId());
|
||||
d.move(parent, objTarget);
|
||||
}
|
||||
else if (objFrom instanceof Folder)
|
||||
{
|
||||
Folder f = (Folder) objFrom;
|
||||
parents = f.getParents();
|
||||
parent = session.getObject(((Folder) parents.get(0)).getId());
|
||||
f.move(parent, objTarget);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertSiteQueryResult(String siteName, Collection<ContentModel> contentModels)
|
||||
{
|
||||
assertSiteQueryResult(testUser, siteName, contentModels);
|
||||
}
|
||||
|
||||
private void assertSiteQueryResult(UserModel user, String siteName, Collection<ContentModel> contentModels)
|
||||
{
|
||||
final List<String> contentNames = contentModels
|
||||
.stream()
|
||||
.map(ContentModel::getName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (final String language : LANGUAGES_TO_CHECK)
|
||||
{
|
||||
Step.STEP("Searching for SITE `" + siteName + "` using `" + language + "` language.");
|
||||
final SearchRequest query = req(language, "SITE:" + siteName + " ");
|
||||
if (contentNames.isEmpty())
|
||||
{
|
||||
searchQueryService.expectNoResultsFromQuery(query, user);
|
||||
}
|
||||
else
|
||||
{
|
||||
Collections.shuffle(contentNames);
|
||||
searchQueryService.expectResultsFromQuery(query, user, contentNames.toArray(String[]::new));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertSiteQueryResult(String site1Name, String operator, String condition, Collection<ContentModel> contentModels)
|
||||
{
|
||||
assertSiteQueryResult(testUser, site1Name, operator, condition, contentModels);
|
||||
}
|
||||
|
||||
private void assertSiteQueryResult(UserModel user, String site1Name, String operator, String condition, Collection<ContentModel> contentModels)
|
||||
{
|
||||
final List<String> contentNames = contentModels
|
||||
.stream()
|
||||
.map(ContentModel::getName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (final String language : LANGUAGES_TO_CHECK)
|
||||
{
|
||||
Step.STEP("Searching for SITE `" + site1Name + "` " + operator + " `" + condition + "` using `" + language + "` language.");
|
||||
final SearchRequest query = req(language, "SITE:" + site1Name + " " + operator + condition);
|
||||
if (contentNames.isEmpty())
|
||||
{
|
||||
searchQueryService.expectNoResultsFromQuery(query, user);
|
||||
}
|
||||
else
|
||||
{
|
||||
Collections.shuffle(contentNames);
|
||||
searchQueryService.expectResultsFromQuery(query, user, contentNames.toArray(String[]::new));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertContentInGivenFileUnderSiteQueryResult(String siteName, String content, Collection<ContentModel> contentModels)
|
||||
{
|
||||
final List<String> contentNames = contentModels
|
||||
.stream()
|
||||
.map(ContentModel::getName)
|
||||
.collect(Collectors.toList());
|
||||
for (final String language : LANGUAGES_TO_CHECK)
|
||||
{
|
||||
Step.STEP("Searching for SITE `" + siteName + "` and content:'" + content + "' using `" + language + "` language.");
|
||||
final SearchRequest query = req(language, "SITE:" + siteName + " AND TEXT:" + content + " ");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, contentNames.toArray(String[]::new));
|
||||
}
|
||||
}
|
||||
|
||||
private void modifyDocument(FileModel file, String newContent)
|
||||
{
|
||||
dataContent.usingUser(siteCreator).usingResource(file)
|
||||
.updateContent(newContent);
|
||||
}
|
||||
|
||||
private void deleteFile(ContentModel contentModel)
|
||||
{
|
||||
dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(contentModel)
|
||||
.deleteContent();
|
||||
}
|
||||
|
||||
private void moveFile(FileModel file, SiteModel sourceSite, SiteModel targetSite)
|
||||
{
|
||||
moveFile(file, sourceSite, targetSite, null);
|
||||
}
|
||||
|
||||
private void moveFile(FileModel file, SiteModel sourceSite, SiteModel targetSite, ContentModel targetFolder)
|
||||
{
|
||||
Session session = dataContent.getContentActions().getCMISSession(siteCreator.getUsername(), siteCreator.getPassword());
|
||||
dataContent.usingUser(siteCreator).getContentActions()
|
||||
.moveTo(
|
||||
session,
|
||||
sourceSite.getId(),
|
||||
file.getName(),
|
||||
targetSite.getId(),
|
||||
targetFolder != null ? targetFolder.getName() : Strings.EMPTY // if empty then target folder is documentLibrary (which is default target location)
|
||||
);
|
||||
}
|
||||
|
||||
private void moveFileOutsideOfSite(FileModel file, SiteModel sourceSite, ContentModel targetContentModel)
|
||||
{
|
||||
Session session = dataContent.getContentActions().getCMISSession(alfrescoHttpClientFactory.getAdminUser(), alfrescoHttpClientFactory.getAdminPassword());
|
||||
ContentActions actions = dataContent.usingAdmin().getContentActions();
|
||||
CmisObject objFrom = actions.getCmisObject(session, sourceSite.getId(), file.getName());
|
||||
CmisObject objTarget = actions.getCmisObject(session, targetContentModel.getCmisLocation());
|
||||
|
||||
List parents;
|
||||
CmisObject parent;
|
||||
if (objFrom instanceof Document)
|
||||
{
|
||||
Document d = (Document) objFrom;
|
||||
parents = d.getParents();
|
||||
parent = session.getObject(((Folder) parents.get(0)).getId());
|
||||
d.move(parent, objTarget);
|
||||
}
|
||||
else if (objFrom instanceof Folder)
|
||||
{
|
||||
Folder f = (Folder) objFrom;
|
||||
parents = f.getParents();
|
||||
parent = session.getObject(((Folder) parents.get(0)).getId());
|
||||
f.move(parent, objTarget);
|
||||
}
|
||||
}
|
||||
|
||||
private FileModel createContentInSite(SiteModel site, String fileName)
|
||||
{
|
||||
return createContentInSite(site, fileName, "Content for " + fileName);
|
||||
}
|
||||
|
||||
private FileModel createContentInSite(SiteModel site, String fileName, String content)
|
||||
{
|
||||
final FileModel file = new FileModel(unique(fileName) + ".txt", FileType.TEXT_PLAIN, content);
|
||||
return dataContent.usingUser(siteCreator)
|
||||
.usingSite(site)
|
||||
.createContent(file);
|
||||
}
|
||||
|
||||
private SiteModel createPublicSite()
|
||||
{
|
||||
return createPublicSite(siteCreator);
|
||||
}
|
||||
|
||||
private SiteModel createPublicSite(UserModel user)
|
||||
{
|
||||
SiteModel createdSite = dataSite.usingUser(user).createPublicRandomSite();
|
||||
Step.STEP("Created public site '" + createdSite.getId() + "'.");
|
||||
return createdSite;
|
||||
}
|
||||
|
||||
private void deleteSite(String siteId)
|
||||
{
|
||||
Step.STEP("Deleting site '" + siteId + "', if it exists");
|
||||
SiteModel siteToDelete = new SiteModel(siteId);
|
||||
if (dataSite.usingAdmin().isSiteCreated(siteToDelete))
|
||||
{
|
||||
dataSite.usingAdmin().deleteSite(siteToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
private static ContentModel contentRoot()
|
||||
{
|
||||
final ContentModel root = new ContentModel("-root-");
|
||||
root.setNodeRef(root.getName());
|
||||
return root;
|
||||
}
|
||||
|
||||
private static String unique(String prefix)
|
||||
{
|
||||
return prefix + "-" + UUID.randomUUID();
|
||||
}
|
||||
}
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.dataprep.AlfrescoHttpClientFactory;
|
||||
import org.alfresco.rest.core.RestWrapper;
|
||||
import org.alfresco.rest.model.RestTagModel;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.*;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
import org.alfresco.utility.report.log.Step;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
public class ElasticsearchTagIndexingTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
private static final Iterable<String> LANGUAGES_TO_CHECK = List.of("afts", "lucene");
|
||||
|
||||
@Autowired
|
||||
ServerHealth serverHealth;
|
||||
|
||||
@Autowired
|
||||
DataUser dataUser;
|
||||
|
||||
@Autowired
|
||||
DataContent dataContent;
|
||||
|
||||
@Autowired
|
||||
private AlfrescoHttpClientFactory alfrescoHttpClientFactory;
|
||||
|
||||
@Autowired
|
||||
SearchQueryService searchQueryService;
|
||||
|
||||
@Autowired
|
||||
protected RestWrapper restClient;
|
||||
|
||||
private UserModel testUser;
|
||||
private FolderModel testFolder;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
testUser = dataUser.createRandomTestUser();
|
||||
testFolder = dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(contentRoot())
|
||||
.createFolder(new FolderModel(unique("FOLDER")));
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH, TestGroup.TAGS, TestGroup.REGRESSION})
|
||||
public void testTAGUseCases()
|
||||
{
|
||||
final String tag1 = unique("TAG1");
|
||||
|
||||
// No result for not existing tag
|
||||
assertTagQueryResult(tag1, List.of());
|
||||
|
||||
// Tag first file - tag is created - expect single result
|
||||
final FileModel file1 = givenFile("test1");
|
||||
tagContent(file1, tag1);
|
||||
assertTagQueryResult(tag1, List.of(file1));
|
||||
|
||||
// Tag another file - tag is reused - expect two items in the result
|
||||
final FileModel file2 = givenFile("test2");
|
||||
tagContent(file2, tag1);
|
||||
assertTagQueryResult(tag1, List.of(file1, file2));
|
||||
|
||||
final String tag2 = unique("TAG2");
|
||||
|
||||
// Just a sanity check - No result for the second tag
|
||||
assertTagQueryResult(tag2, List.of());
|
||||
|
||||
// Tag second file with a new tag
|
||||
final String tag2Id = tagContent(file2, tag2);
|
||||
assertTagQueryResult(tag1, List.of(file1, file2));
|
||||
assertTagQueryResult(tag2, List.of(file2));
|
||||
|
||||
// Tag new file with the second tag
|
||||
final FileModel file3 = givenFile("test3");
|
||||
tagContent(file3, tag2);
|
||||
assertTagQueryResult(tag1, List.of(file1, file2));
|
||||
assertTagQueryResult(tag2, List.of(file2, file3));
|
||||
|
||||
// test disjunction and conjunction
|
||||
assertTagQueryResult(tag1, "OR", tag2, List.of(file1, file2, file3));
|
||||
assertTagQueryResult(tag1, "AND", tag2, List.of(file2));
|
||||
final String unknownTag = unique("unknown");
|
||||
assertTagQueryResult(tag1, "OR", unknownTag, List.of(file1, file2));
|
||||
assertTagQueryResult(tag1, "AND", unknownTag, List.of());
|
||||
|
||||
// Delete file
|
||||
deleteFile(file1);
|
||||
assertTagQueryResult(tag1, List.of(file2));
|
||||
assertTagQueryResult(tag2, List.of(file2, file3));
|
||||
|
||||
// Rename tag
|
||||
final String newTag2 = unique("NEW-TAG2");
|
||||
assertTagQueryResult(newTag2, List.of());
|
||||
renameTag(tag2Id, newTag2);
|
||||
assertTagQueryResult(tag1, List.of(file2));
|
||||
assertTagQueryResult(newTag2, List.of(file2, file3));
|
||||
assertTagQueryResult(tag2, List.of());
|
||||
|
||||
// Delete tag
|
||||
deleteTag(file3, newTag2);
|
||||
assertTagQueryResult(tag1, List.of(file2));
|
||||
assertTagQueryResult(tag2, List.of());
|
||||
assertTagQueryResult(newTag2, List.of(file2));
|
||||
}
|
||||
|
||||
private void assertTagQueryResult(String tag1Name, String operator, String tag2Name, Collection<ContentModel> contentModels)
|
||||
{
|
||||
final List<String> contentNames = contentModels
|
||||
.stream()
|
||||
.map(ContentModel::getName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (final String tag1 : tagVariantsToCheck(tag1Name))
|
||||
{
|
||||
for (final String tag2 : tagVariantsToCheck(tag2Name))
|
||||
{
|
||||
for (final String language : LANGUAGES_TO_CHECK)
|
||||
{
|
||||
Step.STEP("Searching for TAG `" + tag1 + "` " + operator + " `" + tag2 + "` using `" + language + "` language.");
|
||||
final SearchRequest query = req(language, "TAG:" + tag1 + " " + operator + " TAG:" + tag2);
|
||||
if (contentNames.isEmpty())
|
||||
{
|
||||
searchQueryService.expectNoResultsFromQuery(query, testUser);
|
||||
}
|
||||
else
|
||||
{
|
||||
Collections.shuffle(contentNames);
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, contentNames.toArray(String[]::new));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTagQueryResult(String tagName, Collection<ContentModel> contentModels)
|
||||
{
|
||||
final List<String> contentNames = contentModels
|
||||
.stream()
|
||||
.map(ContentModel::getName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (final String tag : tagVariantsToCheck(tagName))
|
||||
{
|
||||
for (final String language : LANGUAGES_TO_CHECK)
|
||||
{
|
||||
Step.STEP("Searching for TAG `" + tag + "` using `" + language + "` language.");
|
||||
final SearchRequest query = req(language, "TAG:" + tag);
|
||||
if (contentNames.isEmpty())
|
||||
{
|
||||
searchQueryService.expectNoResultsFromQuery(query, testUser);
|
||||
}
|
||||
else
|
||||
{
|
||||
Collections.shuffle(contentNames);
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, contentNames.toArray(String[]::new));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Iterable<String> tagVariantsToCheck(String tagName)
|
||||
{
|
||||
return List.of(
|
||||
tagName.toLowerCase(Locale.ROOT),
|
||||
tagName.toUpperCase(Locale.ROOT),
|
||||
"\"" + tagName.toLowerCase(Locale.ROOT) + "\"",
|
||||
"\"" + tagName.toUpperCase(Locale.ROOT) + "\"");
|
||||
}
|
||||
|
||||
private FileModel givenFile(final String fileName)
|
||||
{
|
||||
final FileModel file = new FileModel(unique(fileName), FileType.TEXT_PLAIN, "Content for " + fileName);
|
||||
return dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(testFolder)
|
||||
.createContent(file);
|
||||
}
|
||||
|
||||
private String tagContent(ContentModel contentModel, String tag)
|
||||
{
|
||||
var tagModel = new TagModel(tag);
|
||||
dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(contentModel)
|
||||
.addTagToContent(tagModel);
|
||||
|
||||
final String tagNodeId = dataContent.getContentActions().getTagNodeRef(
|
||||
dataContent.getCurrentUser().getUsername(), dataContent.getCurrentUser().getPassword(),
|
||||
contentModel.getCmisLocation(), tag);
|
||||
assertNotNull(tagNodeId, "Tag node ref must exist.");
|
||||
return tagNodeId;
|
||||
}
|
||||
|
||||
private void deleteFile(ContentModel contentModel)
|
||||
{
|
||||
dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(contentModel)
|
||||
.deleteContent();
|
||||
}
|
||||
|
||||
private void deleteTag(ContentModel contentModel, String tagName)
|
||||
{
|
||||
boolean deleted = dataContent
|
||||
.usingAdmin()
|
||||
.getContentActions()
|
||||
.removeTag(
|
||||
dataContent.getCurrentUser().getUsername(), dataContent.getCurrentUser().getPassword(),
|
||||
contentModel.getCmisLocation(), tagName);
|
||||
assertTrue(deleted, "Tag should be deleted.");
|
||||
}
|
||||
|
||||
private void renameTag(String tagId, String newName)
|
||||
{
|
||||
RestTagModel tag = RestTagModel.builder().id(tagId).create();
|
||||
RestTagModel update = restClient.authenticateUser(dataContent.getAdminUser()).withCoreAPI().usingTag(tag).update(newName);
|
||||
restClient.assertStatusCodeIs(OK);
|
||||
}
|
||||
|
||||
private static ContentModel contentRoot()
|
||||
{
|
||||
final ContentModel root = new ContentModel("-root-");
|
||||
root.setNodeRef(root.getName());
|
||||
return root;
|
||||
}
|
||||
|
||||
private static String unique(String prefix)
|
||||
{
|
||||
return prefix + "-" + UUID.randomUUID();
|
||||
}
|
||||
}
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
import static org.alfresco.utility.data.RandomData.getRandomFile;
|
||||
import static org.alfresco.utility.data.RandomData.getRandomName;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.model.RestTagModel;
|
||||
import org.alfresco.rest.search.RestRequestDefaultsModel;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.Utility;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.ContentModel;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
public class ElasticsearchTemplateSearchTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
private static final String SEARCH_TERM = "sample";
|
||||
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
|
||||
@Autowired
|
||||
private DataContent dataContent;
|
||||
|
||||
@Autowired
|
||||
private SearchQueryService searchQueryService;
|
||||
|
||||
private UserModel testUser;
|
||||
private FolderModel testParentFolder;
|
||||
/** Pre-built ANCESTOR clause for the test parent folder. */
|
||||
private String ancestorClause;
|
||||
private ContentModel fileWithTermInName;
|
||||
private ContentModel fileWithPhraseInContent;
|
||||
private ContentModel fileWithTermInTitle;
|
||||
private ContentModel fileWithTermInDescription;
|
||||
private ContentModel fileWithTermInTag;
|
||||
private ContentModel fileWithDifferentTermInName;
|
||||
private ContentModel folderWithTermInName;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation() throws Exception
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
STEP("Create a dedicated parent folder so all queries can be scoped via ANCESTOR");
|
||||
testParentFolder = createTestParentFolder();
|
||||
ancestorClause = "ANCESTOR:\"" + testParentFolder.getNodeRef() + "\"";
|
||||
|
||||
STEP("Create a test user and few files and folders containing searched term in name, title, description, content and tag");
|
||||
fileWithTermInName = createFile(SEARCH_TERM + ".txt", "some text");
|
||||
fileWithPhraseInContent = createFile(getRandomFile(FileType.TEXT_PLAIN), "Dummy " + SEARCH_TERM + " irrelevant text");
|
||||
fileWithTermInTitle = createRandomFileWithTitle(SEARCH_TERM);
|
||||
fileWithTermInDescription = createRandomFileWithDescription(SEARCH_TERM);
|
||||
fileWithTermInTag = createRandomFileWithTag(SEARCH_TERM);
|
||||
fileWithDifferentTermInName = createFile("dummy.txt", "content without phrase");
|
||||
folderWithTermInName = createFolder(SEARCH_TERM);
|
||||
|
||||
testUser = dataUser.createRandomTestUser();
|
||||
|
||||
STEP("Wait for the batch indexer to index the last-created node under the test parent folder");
|
||||
SearchRequest probe = req("afts", ancestorClause + " AND TYPE:'cm:folder'", Map.of());
|
||||
Utility.sleep(500, 60000, () -> searchQueryService.expectNodeRefsFromQuery(
|
||||
probe, dataUser.getAdminUser(), folderWithTermInName.getNodeRef()));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public void dataCleanup()
|
||||
{
|
||||
STEP("Clean up the test parent folder (cascades to all children) and the test user");
|
||||
if (testParentFolder != null)
|
||||
{
|
||||
dataContent.usingAdmin().usingResource(testParentFolder).deleteContent();
|
||||
}
|
||||
if (testUser != null)
|
||||
{
|
||||
dataUser.deleteUser(testUser);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_simpleTemplate()
|
||||
{
|
||||
STEP("Search for files by name using simple template with one property");
|
||||
Map<String, String> templates = Map.of("_NODE", "%cm:name");
|
||||
String query = ancestorClause + " AND TYPE:'cm:content' AND _NODE:" + SEARCH_TERM;
|
||||
SearchRequest request = req("afts", query, templates);
|
||||
|
||||
searchQueryService.expectNodeRefsFromQuery(request, testUser, fileWithTermInName.getNodeRef());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_simpleTemplateWithPhrase()
|
||||
{
|
||||
STEP("Search for files containing specific phrase using simple template with one property");
|
||||
Map<String, String> templates = Map.of("_NODE", "%TEXT");
|
||||
String query = ancestorClause + " AND TYPE:'cm:content' AND _NODE:\"" + SEARCH_TERM + " irrelevant\"";
|
||||
SearchRequest request = req("afts", query, templates);
|
||||
|
||||
searchQueryService.expectNodeRefsFromQuery(request, testUser, fileWithPhraseInContent.getNodeRef());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_templateWithTwoParameters()
|
||||
{
|
||||
STEP("Search for files using template containing multiple properties");
|
||||
Map<String, String> templates = Map.of("_NODE", "%(cm:name cm:title)");
|
||||
String query = ancestorClause + " AND TYPE:'cm:content' AND _NODE:" + SEARCH_TERM;
|
||||
SearchRequest request = req("afts", query, templates);
|
||||
|
||||
searchQueryService.expectNodeRefsFromQuery(request, testUser, fileWithTermInName.getNodeRef(), fileWithTermInTitle.getNodeRef());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_nestedTemplate()
|
||||
{
|
||||
Map<String, String> templates = Map.of(
|
||||
"_NODE", "%(cm:name cm:title)",
|
||||
"_NODET", "%(_NODEX TAG)",
|
||||
"_NODEX", "%(_NODE cm:description)"
|
||||
|
||||
);
|
||||
STEP("Search for files using more complex two-level nested template containing multiple properties");
|
||||
String query = ancestorClause + " AND TYPE:'cm:content' AND _NODEX:" + SEARCH_TERM;
|
||||
SearchRequest request = req("afts", query, templates);
|
||||
searchQueryService.expectNodeRefsFromQuery(request, testUser,
|
||||
fileWithTermInName.getNodeRef(), fileWithTermInDescription.getNodeRef(), fileWithTermInTitle.getNodeRef());
|
||||
|
||||
STEP("Search for files using three-level nested template containing multiple properties");
|
||||
String queryIncludingTag = ancestorClause + " AND TYPE:'cm:content' AND _NODET:" + SEARCH_TERM;
|
||||
SearchRequest requestIncludingTag = req("afts", queryIncludingTag, templates);
|
||||
searchQueryService.expectNodeRefsFromQuery(requestIncludingTag, testUser,
|
||||
fileWithTermInName.getNodeRef(), fileWithTermInDescription.getNodeRef(), fileWithTermInTitle.getNodeRef(), fileWithTermInTag.getNodeRef());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_templateNameAsQueryDefaultFieldName()
|
||||
{
|
||||
STEP("Search for files using template containing multiple properties, as a default search field");
|
||||
Map<String, String> templates = Map.of("_NODE", "%(cm:name cm:title)");
|
||||
// ANCESTOR must be its own clause; only the bare term uses the default field name.
|
||||
String query = ancestorClause + " AND TYPE:'cm:content' AND " + SEARCH_TERM;
|
||||
SearchRequest request = req("afts", query, templates);
|
||||
request.setDefaults(RestRequestDefaultsModel.builder().defaultFieldName("_NODE").create());
|
||||
|
||||
searchQueryService.expectNodeRefsFromQuery(request, testUser, fileWithTermInName.getNodeRef(), fileWithTermInTitle.getNodeRef());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_templateWithFixedValue()
|
||||
{
|
||||
STEP("Search for files and folders using template containing multiple properties, including a fixed one");
|
||||
Map<String, String> templates = Map.of("_NODE", "%cm:name AND TYPE:'cm:folder'");
|
||||
String query = ancestorClause + " AND _NODE:" + SEARCH_TERM;
|
||||
SearchRequest request = req("afts", query, templates);
|
||||
|
||||
searchQueryService.expectNodeRefsFromQuery(request, testUser, folderWithTermInName.getNodeRef());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_boostedTemplate()
|
||||
{
|
||||
STEP("Search for files using templates and boosts, where second term has higher priority ");
|
||||
Map<String, String> templates = Map.of("_NODE", "%cm:name");
|
||||
String query = ancestorClause + " AND TYPE:'cm:content' AND (_NODE:" + SEARCH_TERM + "^0.5 OR _NODE:dummy^2)";
|
||||
SearchRequest request = req("afts", query, templates);
|
||||
searchQueryService.expectResultsInOrder(request, testUser, fileWithDifferentTermInName.getName(), fileWithTermInName.getName());
|
||||
|
||||
STEP("Search for files using templates and boosts, where first term has higher priority ");
|
||||
String queryInvertedBoost = ancestorClause + " AND TYPE:'cm:content' AND (_NODE:" + SEARCH_TERM + "^2 OR _NODE:dummy^0.5)";
|
||||
SearchRequest requestInvertedBoost = req("afts", queryInvertedBoost, templates);
|
||||
searchQueryService.expectResultsInOrder(requestInvertedBoost, testUser, fileWithTermInName.getName(), fileWithDifferentTermInName.getName());
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.SEARCH})
|
||||
public void testAftsQuery_expandedTemplate()
|
||||
{
|
||||
Map<String, String> templates = Map.of("_NODE", "%cm:name");
|
||||
String query = ancestorClause + " AND TYPE:'cm:content' AND ~_NODE:" + SEARCH_TERM;
|
||||
SearchRequest request = req("afts", query, templates);
|
||||
|
||||
searchQueryService.expectNodeRefsFromQuery(request, testUser, fileWithTermInName.getNodeRef());
|
||||
}
|
||||
|
||||
private FolderModel createTestParentFolder()
|
||||
{
|
||||
ContentModel contentRoot = new ContentModel("-root-");
|
||||
contentRoot.setNodeRef(contentRoot.getName());
|
||||
FolderModel parent = new FolderModel(getRandomName("templateSearchTests-"));
|
||||
return dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(contentRoot)
|
||||
.createFolder(parent);
|
||||
}
|
||||
|
||||
private ContentModel createRandomFileWithTitle(String title)
|
||||
{
|
||||
return createRandomFile(title, null, null);
|
||||
}
|
||||
|
||||
private ContentModel createRandomFileWithDescription(String description)
|
||||
{
|
||||
return createRandomFile(null, description, null);
|
||||
}
|
||||
|
||||
private ContentModel createRandomFileWithTag(String tag)
|
||||
{
|
||||
return createRandomFile(null, null, tag);
|
||||
}
|
||||
|
||||
private ContentModel createRandomFile(String title, String description, String tag)
|
||||
{
|
||||
return createFile(getRandomFile(FileType.TEXT_PLAIN), getRandomName("dummy text "), title, description, tag);
|
||||
}
|
||||
|
||||
private ContentModel createFile(String filename, String content)
|
||||
{
|
||||
return createFile(filename, content, null, null, null);
|
||||
}
|
||||
|
||||
private ContentModel createFile(String filename, String content, String title, String description, String tag)
|
||||
{
|
||||
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
|
||||
fileModel.setTitle(title);
|
||||
fileModel.setDescription(description);
|
||||
|
||||
// Create inside the dedicated parent folder so queries scoped with ANCESTOR will pick this up
|
||||
// (and so bootstrap / cross-class content does NOT match those queries).
|
||||
FileModel file = dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(testParentFolder)
|
||||
.createContent(fileModel);
|
||||
|
||||
if (StringUtils.isNotBlank(tag))
|
||||
{
|
||||
dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(file)
|
||||
.addTagToContent(RestTagModel.builder().tag(tag).create());
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private ContentModel createFolder(String folderName)
|
||||
{
|
||||
return createFolder(folderName, null, null, null);
|
||||
}
|
||||
|
||||
private ContentModel createFolder(String folderName, String title, String description, String tag)
|
||||
{
|
||||
FolderModel folderModel = new FolderModel(folderName, title, description);
|
||||
|
||||
FolderModel folder = dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(testParentFolder)
|
||||
.createFolder(folderModel);
|
||||
|
||||
if (StringUtils.isNotBlank(tag))
|
||||
{
|
||||
dataContent
|
||||
.usingAdmin()
|
||||
.usingResource(folder)
|
||||
.addTagToContent(RestTagModel.builder().tag(tag).create());
|
||||
}
|
||||
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
/**
|
||||
* Tests verifying a range of simple AFTS queries using the cm:name field.
|
||||
*/
|
||||
public class ElasticsearchTokenisationTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
private static final String ALPHABETIC_NO_SPACE = "TestFileabc.txt";
|
||||
private static final String NUMERIC_NO_SPACE = "TestFile123.txt";
|
||||
private static final String ALPHABETIC_WITH_SPACE = "TestFile abc.txt";
|
||||
private static final String NUMERIC_WITH_SPACE = "TestFile 123.txt";
|
||||
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
|
||||
@Autowired
|
||||
private DataContent dataContent;
|
||||
|
||||
@Autowired
|
||||
private DataSite dataSite;
|
||||
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
|
||||
@Autowired
|
||||
protected SearchQueryService searchQueryService;
|
||||
|
||||
private UserModel user;
|
||||
private SiteModel siteModel;
|
||||
|
||||
/**
|
||||
* Create a site containing four documents.
|
||||
*/
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
user = dataUser.createRandomTestUser();
|
||||
siteModel = dataSite.usingUser(user).createPrivateRandomSite();
|
||||
|
||||
createContent(ALPHABETIC_NO_SPACE, "", siteModel, user);
|
||||
createContent(NUMERIC_NO_SPACE, "", siteModel, user);
|
||||
createContent(ALPHABETIC_WITH_SPACE, "", siteModel, user);
|
||||
createContent(NUMERIC_WITH_SPACE, "", siteModel, user);
|
||||
}
|
||||
|
||||
/** Check that searching for "TestFileabc.txt" only returns that file. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchNoSpaceAlphabetic()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("name:\"TestFileabc.txt\""), user, ALPHABETIC_NO_SPACE);
|
||||
}
|
||||
|
||||
/** Check that searching for "TestFile abc.txt" only returns that file. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchWithSpaceAlphabetic()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("name:\"TestFile abc.txt\""), user, ALPHABETIC_WITH_SPACE);
|
||||
}
|
||||
|
||||
/** Check that searching for "TestFile123.txt" returns both files with numbers in. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchNoSpaceNumeric()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("name:\"TestFile123.txt\""), user, NUMERIC_NO_SPACE, NUMERIC_WITH_SPACE);
|
||||
}
|
||||
|
||||
/** Check that searching for "TestFile 123.txt" returns both files with numbers in. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchWithSpaceNumeric()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("name:\"TestFile 123.txt\""), user, NUMERIC_NO_SPACE, NUMERIC_WITH_SPACE);
|
||||
}
|
||||
|
||||
/** Check that searching for "TestFile" doesn't return TestFileabc.txt. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchBaseFileName()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("name:\"TestFile\""), user, NUMERIC_NO_SPACE, ALPHABETIC_WITH_SPACE, NUMERIC_WITH_SPACE);
|
||||
}
|
||||
|
||||
/** Check that searching for TestFileabc.txt with no quotes returns just that file. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchNoQuotesNoSpaceAlphabetic()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("name:TestFileabc.txt"), user, ALPHABETIC_NO_SPACE);
|
||||
}
|
||||
|
||||
/** Check that searching for TestFile123.txt with no quotes returns both files with numbers in. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchNoQuotesNoSpaceNumeric()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("name:TestFile123.txt"), user, NUMERIC_NO_SPACE, NUMERIC_WITH_SPACE);
|
||||
}
|
||||
|
||||
/** Check that searching for TestFile doesn't return TestFileabc.txt. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchBaseFileNameNoQuotes()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("name:TestFile"), user, NUMERIC_NO_SPACE, ALPHABETIC_WITH_SPACE, NUMERIC_WITH_SPACE);
|
||||
}
|
||||
|
||||
/** Check that including an escaped space (_x0020_) in double quotes means that nothing is matched. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchEncodedSpaceInQuotes()
|
||||
{
|
||||
searchQueryService.expectNoResultsFromQuery(req("name:\"TestFile_x0020_abc.txt\""), user);
|
||||
}
|
||||
|
||||
/** Check that searching for 'TestFileabc.txt' only returns that file. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchNoSpaceAlphabeticSingleQuote()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("name:'TestFileabc.txt'"), user, ALPHABETIC_NO_SPACE);
|
||||
}
|
||||
|
||||
/** Check that searching for 'TestFile abc.txt' only returns that file. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchWithSpaceAlphabeticSingleQuote()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("name:'TestFile abc.txt'"), user, ALPHABETIC_WITH_SPACE);
|
||||
}
|
||||
|
||||
/** Check that searching for 'TestFile123.txt' returns both files with numbers in. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchNoSpaceNumericSingleQuote()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("name:'TestFile123.txt'"), user, NUMERIC_NO_SPACE, NUMERIC_WITH_SPACE);
|
||||
}
|
||||
|
||||
/** Check that searching for 'TestFile 123.txt' returns both files with numbers in. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchWithSpaceNumericSingleQuote()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("name:'TestFile 123.txt'"), user, NUMERIC_NO_SPACE, NUMERIC_WITH_SPACE);
|
||||
}
|
||||
|
||||
/** Check that searching for 'TestFile' doesn't return TestFileabc.txt. */
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchBaseFileNameSingleQuote()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("name:'TestFile'"), user, NUMERIC_NO_SPACE, ALPHABETIC_WITH_SPACE, NUMERIC_WITH_SPACE);
|
||||
}
|
||||
|
||||
private FileModel createContent(String filename, String content, SiteModel site, UserModel user)
|
||||
{
|
||||
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
|
||||
return dataContent.usingUser(user).usingSite(site)
|
||||
.createContent(fileModel);
|
||||
}
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.repo.resource.Categories;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
|
||||
/**
|
||||
* Tests verifying indexing of secondary children and ANCESTOR and CATEGORY index in Elasticsearch.
|
||||
*/
|
||||
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert", "PMD.MethodNamingConventions", "PMD.LocalVariableNamingConventions"}) // these are testng tests and use searchQueryService.expectResultsFromQuery for assertion
|
||||
public class NodeWithCategoryIndexingTests extends NodesSecondaryChildrenRelatedTests
|
||||
{
|
||||
|
||||
@Autowired
|
||||
private Categories categories;
|
||||
|
||||
/* A --- B (folders) \____ \ K --- L (categories) */
|
||||
@BeforeClass(alwaysRun = true)
|
||||
@Override
|
||||
public void dataPreparation()
|
||||
{
|
||||
super.dataPreparation();
|
||||
|
||||
// given
|
||||
STEP("Create nested folders in site's Document Library.");
|
||||
folders.add().nestedRandomFolders(A, B).create();
|
||||
|
||||
STEP("Create nested categories.");
|
||||
categories.add().nestedRandomCategories(K, L).create();
|
||||
|
||||
STEP("Link folders to category.");
|
||||
folders.modify(A).linkTo(categories.get(L));
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testParentQueryAgainstCategory()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that searching by PARENT and category will find one descendant node: categoryL.");
|
||||
SearchRequest query = req("PARENT:" + categories.get(K).getId());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, categories.get(L).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testParentQueryAgainstFolder()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that searching by PARENT and category will find one descendant node: folderA.");
|
||||
SearchRequest query = req("PARENT:" + categories.get(L).getId());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, folders.get(A).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testParentQueryAgainstFolderAfterCategoryDeletion()
|
||||
{
|
||||
// given
|
||||
STEP("Create nested folders in site's Document Library.");
|
||||
folders.add().randomFolder(C).create();
|
||||
|
||||
STEP("Create nested categories.");
|
||||
categories.add().randomCategory(M).create();
|
||||
|
||||
STEP("Link folders to category.");
|
||||
folders.modify(C).linkTo(categories.get(M));
|
||||
|
||||
// when
|
||||
STEP("Verify that searching by PARENT and category will find one descendant node: folderC.");
|
||||
SearchRequest query = req("PARENT:" + categories.get(M).getId());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, folders.get(C).getName());
|
||||
|
||||
// then
|
||||
STEP("Delete categoryM.");
|
||||
categories.delete(categories.get(M));
|
||||
|
||||
STEP("Verify that searching by PARENT and deleted category will find no descendant nodes.");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH, enabled = false) // re-enable after ACS-6588, ACS-6592
|
||||
public void testParentQueryAgainstFolderAfterParentCategoryDeletion()
|
||||
{
|
||||
// given
|
||||
STEP("Create nested folders in site's Document Library.");
|
||||
folders.add().randomFolder(X).create();
|
||||
|
||||
STEP("Create nested categories.");
|
||||
categories.add().nestedRandomCategories(P, Q).create();
|
||||
|
||||
STEP("Link folders to category.");
|
||||
folders.modify(X).linkTo(categories.get(Q));
|
||||
|
||||
// when
|
||||
STEP("Verify that searching by PARENT and category will find one descendant node: folderC.");
|
||||
SearchRequest query = req("PARENT:" + categories.get(Q).getId());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, folders.get(X).getName());
|
||||
|
||||
// then
|
||||
STEP("Delete categoryP.");
|
||||
categories.delete(categories.get(P));
|
||||
|
||||
STEP("Verify that searching by PARENT and deleted category will find no descendant nodes.");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSearchByPath()
|
||||
{
|
||||
// given
|
||||
String Kname = categories.get(K).getName();
|
||||
String Lname = categories.get(L).getName();
|
||||
String Aname = folders.get(A).getName();
|
||||
|
||||
// then
|
||||
STEP("Verify that searching by PATH and category will find: folderA");
|
||||
SearchRequest query = req(format("PATH:'/cm:categoryRoot/cm:generalclassifiable/cm:%s/cm:%s/cm:%s'", Kname, Lname, Aname));
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, folders.get(A).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void ensureCategoriesAreNotTransitive_lookingForExactChildren()
|
||||
{
|
||||
// given
|
||||
String Kname = categories.get(K).getName();
|
||||
String Lname = categories.get(L).getName();
|
||||
String Aname = folders.get(A).getName();
|
||||
String Bname = folders.get(B).getName();
|
||||
|
||||
// then
|
||||
STEP("Verify that searching by PATH for nested folder will return no results (Dependency to category is not transitive)");
|
||||
SearchRequest query = req(format("PATH:'/cm:categoryRoot/cm:generalclassifiable/cm:%s/cm:%s/cm:%s/cm:%s'", Kname, Lname, Aname, Bname));
|
||||
searchQueryService.expectNoResultsFromQuery(query, testUser);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void ensureCategoriesAreNotTransitive_lookingForAllDescendants()
|
||||
{
|
||||
// given
|
||||
String Kname = categories.get(K).getName();
|
||||
String Lname = categories.get(L).getName();
|
||||
|
||||
// then
|
||||
STEP("Verify that searching by PATH and category will find: folderA");
|
||||
SearchRequest query = req(format("PATH:'/cm:categoryRoot/cm:generalclassifiable/cm:%s/cm:%s//*'", Kname, Lname));
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, folders.get(A).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void ensureCategoriesAreNotTransitive_lookingForAllDescendants_byParentCategory()
|
||||
{
|
||||
// given
|
||||
String Kname = categories.get(K).getName();
|
||||
|
||||
// then
|
||||
STEP("Verify that searching by PATH and category will find: folderA");
|
||||
SearchRequest query = req(format("PATH:'/cm:categoryRoot/cm:generalclassifiable/cm:%s//*'", Kname));
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, categories.get(L).getName(), folders.get(A).getName());
|
||||
}
|
||||
}
|
||||
+368
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.utility.Utility;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
|
||||
/**
|
||||
* Tests verifying indexing of secondary children and ANCESTOR index in Elasticsearch.
|
||||
*/
|
||||
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"}) // these are TAS E2E tests and use searchQueryService.expectResultsFromQuery for assertion
|
||||
public class NodesSecondaryAncestorIndexingTests extends NodesSecondaryChildrenRelatedTests
|
||||
{
|
||||
|
||||
private FileModel fileInP;
|
||||
|
||||
/**
|
||||
* Creates a user and a private site containing below hierarchy of folders.
|
||||
*
|
||||
* <pre>
|
||||
* Site
|
||||
* DL (Document Library)
|
||||
* += fA += fB += fC (folderC)
|
||||
* / / |
|
||||
* + + +
|
||||
* += fK += fL += fM
|
||||
* | +
|
||||
* + |
|
||||
* += fX += fY += fZ
|
||||
* += fP += file -+ fA
|
||||
* += fQ
|
||||
* += fR
|
||||
* += fS
|
||||
* </pre>
|
||||
*
|
||||
* Parent += Child - primary parent-child relationship Parent +- Child - secondary parent-child relationship
|
||||
*/
|
||||
@BeforeClass(alwaysRun = true)
|
||||
@Override
|
||||
public void dataPreparation()
|
||||
{
|
||||
super.dataPreparation();
|
||||
|
||||
// given
|
||||
STEP("Create few sets of nested folders in site's Document Library.");
|
||||
folders.add().nestedRandomFolders(A, B, C).create();
|
||||
folders.add().nestedRandomFolders(K, L, M).create();
|
||||
folders.add().nestedRandomFolders(X, Y, Z).create();
|
||||
folders.add().randomFolders(P, Q, R, S).create();
|
||||
fileInP = folders.modify(P).add().randomFile().create();
|
||||
|
||||
STEP("Create few secondary parent-child relationships.");
|
||||
folders.modify(K).add().secondaryContent(folders.get(B));
|
||||
folders.modify(X).add().secondaryContent(folders.get(K));
|
||||
folders.modify(L).add().secondaryContent(folders.get(C), folders.get(Y));
|
||||
folders.modify(M).add().secondaryContent(folders.get(C));
|
||||
folders.modify(A).add().secondaryContent(fileInP);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryAncestorWithNodeHavingOneSecondaryChild()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that searching by ANCESTOR and folderM will find one descendant node: folderC.");
|
||||
SearchRequest query = req("ANCESTOR:" + folders.get(M).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
folders.get(C).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryAncestorWithNodeHavingTwoSecondaryChildren()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that searching by ANCESTOR and folderL will find nodes: folderM, folderC, folderY and folderZ.");
|
||||
SearchRequest queryAncestorL = req("ANCESTOR:" + folders.get(L).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(queryAncestorL, testUser,
|
||||
// primary descendant
|
||||
folders.get(M).getName(),
|
||||
// secondary descendants
|
||||
folders.get(C).getName(),
|
||||
folders.get(Y).getName(),
|
||||
folders.get(Z).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryAncestorWithDocumentAsSecondaryChild()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that searching by ANCESTOR and folderA will find nodes: folderB, folderC and fileInP.");
|
||||
SearchRequest query = req("ANCESTOR:" + folders.get(A).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// primary descendants
|
||||
folders.get(B).getName(),
|
||||
folders.get(C).getName(),
|
||||
// secondary descendant
|
||||
fileInP.getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryAncestorWithNodeHavingComplexSecondaryRelationship()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that all descendant of folderX can be found.");
|
||||
SearchRequest query = req("ANCESTOR:" + folders.get(X).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// primary descendants
|
||||
folders.get(Y).getName(),
|
||||
folders.get(Z).getName(),
|
||||
// secondary descendants
|
||||
folders.get(B).getName(),
|
||||
folders.get(C).getName(),
|
||||
folders.get(K).getName(),
|
||||
folders.get(L).getName(),
|
||||
folders.get(M).getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that removing secondary parent-child relationship will result in updating ES index: ANCESTOR. Test changes below folders hierarchy:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fQ
|
||||
* +
|
||||
* |
|
||||
* += fR
|
||||
* </pre>
|
||||
*
|
||||
* into:
|
||||
*
|
||||
* <pre>
|
||||
* DL += fQ += fR
|
||||
* </pre>
|
||||
*/
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryAncestorWithDeletedSecondaryRelationship()
|
||||
{
|
||||
// given
|
||||
STEP("Add to folderQ a secondary child folderR and verify if it can be found using ANCESTOR index and secondary child node reference.");
|
||||
folders.modify(Q).add().secondaryContent(folders.get(R));
|
||||
|
||||
STEP("Verify that searching by ANCESTOR and folderQ will find secondary descendant node: folderR.");
|
||||
SearchRequest query = req("ANCESTOR:" + folders.get(Q).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// secondary descendant
|
||||
folders.get(R).getName());
|
||||
|
||||
// when
|
||||
STEP("Delete the secondary parent-child relationship between folderQ and FolderR.");
|
||||
folders.modify(Q).remove().secondaryContent(folders.get(R));
|
||||
|
||||
// then
|
||||
STEP("Verify that folderQ cannot be found by ANCESTOR and folderQ anymore.");
|
||||
searchQueryService.expectNoResultsFromQuery(query, testUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that removing a node D (fD) having a secondary children relationship will remove the relationships and update ANCESTOR index in ES. Test changes below folders hierarchy from:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fQ
|
||||
* +
|
||||
* |
|
||||
* += fE += fF
|
||||
* +
|
||||
* |
|
||||
* += fR
|
||||
* </pre>
|
||||
*
|
||||
* into:
|
||||
*
|
||||
* <pre>
|
||||
* DL += fQ += fR
|
||||
* </pre>
|
||||
*/
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryAncestorWithDeletedSecondaryParentNode()
|
||||
{
|
||||
// given
|
||||
STEP("Create two nested folders (E and F) in Document Library.");
|
||||
FolderModel folderE = folders.add().randomFolder("E").create();
|
||||
FolderModel folderF = folders.modify(folderE).add().randomFolder("F").create();
|
||||
STEP("Make folderE a secondary children of folderQ and folderR a secondary children of folderE.");
|
||||
folders.modify(Q).add().secondaryContent(folderE);
|
||||
folders.modify(folderE).add().secondaryContent(folders.get(R));
|
||||
|
||||
STEP("Verify that searching by ANCESTOR and folderQ will find its secondary descendant: folderE, folderF and folderR.");
|
||||
SearchRequest queryAncestorQ = req("ANCESTOR:" + folders.get(Q).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(queryAncestorQ, testUser,
|
||||
// secondary descendants
|
||||
folderE.getName(),
|
||||
folderF.getName(),
|
||||
folders.get(R).getName());
|
||||
|
||||
// when
|
||||
STEP("Delete folderE with its content.");
|
||||
folders.modify(folderE).delete();
|
||||
|
||||
// then
|
||||
STEP("Verify that searching by ANCESTOR and folderQ will not find any nodes.");
|
||||
searchQueryService.expectNoResultsFromQuery(queryAncestorQ, testUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that moving folderD (fD) containing secondary children from hierarchy:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fQ += fD +- fP += file
|
||||
* += fR
|
||||
* </pre>
|
||||
*
|
||||
* to:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fQ
|
||||
* += fR += fD +- fP += file
|
||||
* </pre>
|
||||
*
|
||||
* will update ANCESTOR index in ES.
|
||||
*/
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryAncestorWithMovedSecondaryParentNode()
|
||||
{
|
||||
// given
|
||||
STEP("Create folderD inside folderQ, and add folderP to D as a secondary child.");
|
||||
FolderModel folderD = folders.modify(Q).add().randomFolder("D").create();
|
||||
folders.modify(folderD).add().secondaryContent(folders.get(P));
|
||||
|
||||
STEP("Verify that searching by ANCESTOR and folderQ will find its primary and secondary descendant nodes: folderD, folderP and file.");
|
||||
SearchRequest queryAncestorQ = req("ANCESTOR:" + folders.get(Q).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(queryAncestorQ, testUser,
|
||||
// primary descendant
|
||||
folderD.getName(),
|
||||
// secondary descendants
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName());
|
||||
STEP("Verify that searching by ANCESTOR and folderR will not find any descendant nodes.");
|
||||
SearchRequest queryAncestorR = req("ANCESTOR:" + folders.get(R).getNodeRef());
|
||||
searchQueryService.expectNoResultsFromQuery(queryAncestorR, testUser);
|
||||
|
||||
// when
|
||||
STEP("Move folderD from folderQ to folderR.");
|
||||
folders.modify(folderD).moveTo(folders.get(R));
|
||||
|
||||
// then
|
||||
STEP("Verify that search result for ANCESTOR and folderQ will not find any descendant anymore.");
|
||||
searchQueryService.expectNoResultsFromQuery(queryAncestorQ, testUser);
|
||||
STEP("Verify that searching by ANCESTOR and folderR will find its primary and secondary descendant nodes: folderD, folderP and file.");
|
||||
searchQueryService.expectResultsFromQuery(queryAncestorR, testUser,
|
||||
// primary descendant
|
||||
folderD.getName(),
|
||||
// secondary descendants
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName());
|
||||
|
||||
STEP("Clean-up - delete folderD.");
|
||||
folders.modify(folderD).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that copying folder will also result in copying folder's secondary children and update ANCESTOR index in ES. Test changes below folders hierarchy:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fS += fG += fH
|
||||
* +
|
||||
* /
|
||||
* += fP += file
|
||||
* += fT
|
||||
* </pre>
|
||||
*
|
||||
* into:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fS += fG += fH
|
||||
* +
|
||||
* /
|
||||
* += fP += file
|
||||
* \
|
||||
* +
|
||||
* += fT += fG-c += fH-c
|
||||
* </pre>
|
||||
*/
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryAncestorWithCopiedSecondaryParentNode() throws InterruptedException
|
||||
{
|
||||
// given
|
||||
STEP("Create nested folders (G and H) inside folderS and folderT in Document Library. Make folderP a secondary child of folderG.");
|
||||
FolderModel folderG = folders.modify(S).add().randomFolder("G").create();
|
||||
FolderModel folderH = folders.modify(folderG).add().randomFolder("H").create();
|
||||
FolderModel folderT = folders.add().randomFolder("T").create();
|
||||
folders.modify(folderG).add().secondaryContent(folders.get(P));
|
||||
|
||||
STEP("Verify that searching by ANCESTOR and folderS will find its descendant nodes: folderG, folderH, folderP and file in P.");
|
||||
SearchRequest queryAncestorS = req("ANCESTOR:" + folders.get(S).getNodeRef());
|
||||
Utility.sleep(500, 60000, () -> searchQueryService.expectResultsFromQuery(queryAncestorS, testUser,
|
||||
// primary descendants
|
||||
folderG.getName(),
|
||||
folderH.getName(),
|
||||
// secondary descendants
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName()));
|
||||
STEP("Verify that searching by ANCESTOR and folderT will not find any nodes.");
|
||||
SearchRequest queryAncestorT = req("ANCESTOR:" + folderT.getNodeRef());
|
||||
searchQueryService.expectNoResultsFromQuery(queryAncestorT, testUser);
|
||||
|
||||
// when
|
||||
STEP("Copy folderG with its content to folderT.");
|
||||
FolderModel folderGCopy = folders.modify(folderG).copyTo(folderT);
|
||||
|
||||
// then
|
||||
STEP("Verify that searching by ANCESTOR and folderS will find its descendant nodes: folderG, folderH, folderP and file in P.");
|
||||
searchQueryService.expectResultsFromQuery(queryAncestorS, testUser,
|
||||
// primary descendants
|
||||
folderG.getName(),
|
||||
folderH.getName(),
|
||||
// secondary descendants
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName());
|
||||
STEP("Verify that searching by ANCESTOR and folderT will find its descendant nodes: folderG-copy, folderH-copy, folderP, file.");
|
||||
searchQueryService.expectResultsFromQuery(queryAncestorT, testUser,
|
||||
// primary descendants
|
||||
folderGCopy.getName(),
|
||||
folderH.getName(), // the same name as folderH-copy
|
||||
// secondary descendants
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName());
|
||||
|
||||
STEP("Clean-up - delete folderG and folderT (with G's copy).");
|
||||
folders.modify(folderG).delete();
|
||||
folders.modify(folderT).delete();
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
|
||||
import org.alfresco.rest.core.RestWrapper;
|
||||
import org.alfresco.rest.repo.resource.Folders;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
public abstract class NodesSecondaryChildrenRelatedTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
@SuppressWarnings("PMD.OneDeclarationPerLine")
|
||||
protected static final String A = "A", B = "B", C = "C", K = "K", L = "L", M = "M", P = "P", Q = "Q", R = "R", S = "S", X = "X", Y = "Y", Z = "Z";
|
||||
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
@Autowired
|
||||
private DataSite dataSite;
|
||||
@Autowired
|
||||
protected DataContent dataContent;
|
||||
@Autowired
|
||||
protected RestWrapper restClient;
|
||||
@Autowired
|
||||
protected SearchQueryService searchQueryService;
|
||||
|
||||
protected UserModel testUser;
|
||||
protected SiteModel testSite;
|
||||
protected Folders folders;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
STEP("Verify environment health.");
|
||||
serverHealth.isServerReachable();
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
STEP("Create a test user and private site.");
|
||||
testUser = dataUser.createRandomTestUser();
|
||||
testSite = dataSite.usingUser(testUser).createPrivateRandomSite();
|
||||
folders = new Folders(dataContent, restClient, testUser, testSite);
|
||||
}
|
||||
|
||||
@AfterClass(alwaysRun = true)
|
||||
public void dataCleanUp()
|
||||
{
|
||||
STEP("Clean up data after tests.");
|
||||
dataSite.usingUser(testUser).deleteSite(testSite);
|
||||
dataUser.usingAdmin().deleteUser(testUser);
|
||||
}
|
||||
}
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
|
||||
/**
|
||||
* Tests verifying indexing of secondary children and PARENT index in Elasticsearch.
|
||||
*/
|
||||
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"}) // these are TAS E2E tests and use searchQueryService.expectResultsFromQuery for assertion
|
||||
public class NodesSecondaryParentIndexingTests extends NodesSecondaryChildrenRelatedTests
|
||||
{
|
||||
|
||||
private FileModel fileInP;
|
||||
|
||||
/**
|
||||
* Creates a user and a private site containing below hierarchy of folders.
|
||||
*
|
||||
* <pre>
|
||||
* Site
|
||||
* DL (Document Library)
|
||||
* += fA += fB += fC (folderC)
|
||||
* / / |
|
||||
* + + +
|
||||
* += fK += fL += fM
|
||||
* | +
|
||||
* + |
|
||||
* += fX += fY += fZ
|
||||
* += fP += file -+ fA
|
||||
* += fQ
|
||||
* += fR
|
||||
* += fS
|
||||
* </pre>
|
||||
*
|
||||
* Parent += Child - primary parent-child relationship Parent +- Child - secondary parent-child relationship
|
||||
*/
|
||||
@BeforeClass(alwaysRun = true)
|
||||
@Override
|
||||
public void dataPreparation()
|
||||
{
|
||||
super.dataPreparation();
|
||||
|
||||
// given
|
||||
STEP("Create few sets of nested folders in site's Document Library.");
|
||||
folders.add().nestedRandomFolders(A, B, C).create();
|
||||
folders.add().nestedRandomFolders(K, L, M).create();
|
||||
folders.add().nestedRandomFolders(X, Y, Z).create();
|
||||
folders.add().randomFolders(P, Q, R, S).create();
|
||||
fileInP = folders.modify(P).add().randomFile().create();
|
||||
|
||||
STEP("Create few secondary parent-child relationships.");
|
||||
folders.modify(K).add().secondaryContent(folders.get(B));
|
||||
folders.modify(X).add().secondaryContent(folders.get(K));
|
||||
folders.modify(L).add().secondaryContent(folders.get(C), folders.get(Y));
|
||||
folders.modify(M).add().secondaryContent(folders.get(C));
|
||||
folders.modify(A).add().secondaryContent(fileInP);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryParentWithNodeHavingOneSecondaryChild()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that searching by PARENT and folderM will find node folderC.");
|
||||
SearchRequest query = req("PARENT:" + folders.get(M).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
folders.get(C).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryParentWithNodeHavingTwoSecondaryChildren()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that searching by PARENT and folderL will find nodes: folderM, FolderC and folderY.");
|
||||
SearchRequest query = req("PARENT:" + folders.get(L).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// primary child
|
||||
folders.get(M).getName(),
|
||||
// secondary children
|
||||
folders.get(C).getName(),
|
||||
folders.get(Y).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryParentWithDocumentAsSecondaryChild()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that searching by PARENT and folderA will find nodes: folderB and file.");
|
||||
SearchRequest query = req("PARENT:" + folders.get(A).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// primary child
|
||||
folders.get(B).getName(),
|
||||
// secondary child
|
||||
fileInP.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that removing secondary parent-child relationship will result in updating ES index: PARENT. Test changes below folders hierarchy:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fQ
|
||||
* +
|
||||
* |
|
||||
* += fR
|
||||
* </pre>
|
||||
*
|
||||
* into:
|
||||
*
|
||||
* <pre>
|
||||
* DL += fQ += fR
|
||||
* </pre>
|
||||
*/
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryParentWithDeletedSecondaryRelationship()
|
||||
{
|
||||
// given
|
||||
STEP("Add to folderQ a secondary child folderR and verify if it can be found using PARENT index and secondary parent node reference.");
|
||||
folders.modify(Q).add().secondaryContent(folders.get(R));
|
||||
|
||||
SearchRequest query = req("PARENT:" + folders.get(Q).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// secondary child
|
||||
folders.get(R).getName());
|
||||
|
||||
// when
|
||||
STEP("Delete the secondary parent relationship between folderQ and FolderR and verify that folderR cannot be found by PARENT and folderQ anymore.");
|
||||
folders.modify(Q).remove().secondaryContent(folders.get(R));
|
||||
|
||||
// then
|
||||
searchQueryService.expectNoResultsFromQuery(query, testUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that removing a node D (fD) having a secondary children relationship will remove the relationships and update PARENT index in ES. Test changes below folders hierarchy from:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fQ
|
||||
* +
|
||||
* |
|
||||
* += fE += fF
|
||||
* +
|
||||
* |
|
||||
* += fR
|
||||
* </pre>
|
||||
*
|
||||
* into:
|
||||
*
|
||||
* <pre>
|
||||
* DL += fQ += fR
|
||||
* </pre>
|
||||
*/
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryParentWithDeletedSecondaryParentNode()
|
||||
{
|
||||
// given
|
||||
STEP("Create two nested folders (E and F) in Document Library.");
|
||||
FolderModel folderE = folders.add().randomFolder("E").create();
|
||||
FolderModel folderF = folders.modify(folderE).add().randomFolder("F").create();
|
||||
STEP("Make folderE a secondary children of folderQ and folderR a secondary children of folderE.");
|
||||
folders.modify(Q).add().secondaryContent(folderE);
|
||||
folders.modify(folderE).add().secondaryContent(folders.get(R));
|
||||
|
||||
STEP("Verify that searching by PARENT and folderQ will find its secondary child: folderE.");
|
||||
SearchRequest queryParentQ = req("PARENT:" + folders.get(Q).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(queryParentQ, testUser,
|
||||
// secondary child
|
||||
folderE.getName());
|
||||
STEP("Verify that searching by PARENT and folderE will find its primary and secondary children: folderF and folderR.");
|
||||
SearchRequest queryParentD = req("PARENT:" + folderE.getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(queryParentD, testUser,
|
||||
// primary child
|
||||
folderF.getName(),
|
||||
// secondary child
|
||||
folders.get(R).getName());
|
||||
|
||||
// when
|
||||
STEP("Delete folderE and verify that PARENT was updated for nodes folderQ and folderR.");
|
||||
folders.modify(folderE).delete();
|
||||
|
||||
// then
|
||||
searchQueryService.expectNoResultsFromQuery(queryParentQ, testUser);
|
||||
searchQueryService.expectNoResultsFromQuery(queryParentD, testUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that moving folder D (fD) containing secondary children from hierarchy:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fQ += fD +- fP += file
|
||||
* += fR
|
||||
* </pre>
|
||||
*
|
||||
* to:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fQ
|
||||
* += fR += fD +- fP += file
|
||||
* </pre>
|
||||
*
|
||||
* will update PARENT index in ES.
|
||||
*/
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryParentWithMovedSecondaryParentNode()
|
||||
{
|
||||
// given
|
||||
STEP("Create folderD inside folderQ, and add folderP to D as a secondary child.");
|
||||
FolderModel folderD = folders.modify(Q).add().randomFolder("D").create();
|
||||
folders.modify(folderD).add().secondaryContent(folders.get(P));
|
||||
|
||||
STEP("Verify that searching by PARENT and folderD will find node folderP.");
|
||||
SearchRequest queryParentD = req("PARENT:" + folderD.getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(queryParentD, testUser,
|
||||
// secondary child
|
||||
folders.get(P).getName());
|
||||
STEP("Verify that searching by PARENT and folderQ will find node folderD.");
|
||||
SearchRequest queryParentQ = req("PARENT:" + folders.get(Q).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(queryParentQ, testUser,
|
||||
// primary child
|
||||
folderD.getName());
|
||||
|
||||
// when
|
||||
STEP("Move folderD from folderQ to folderR.");
|
||||
folders.modify(folderD).moveTo(folders.get(R));
|
||||
|
||||
// then
|
||||
STEP("Verify that search result for PARENT and folderD didn't change.");
|
||||
searchQueryService.expectResultsFromQuery(queryParentD, testUser,
|
||||
// secondary child
|
||||
folders.get(P).getName());
|
||||
STEP("Verify that searching by PARENT and folderQ doesn't return any node anymore.");
|
||||
searchQueryService.expectNoResultsFromQuery(queryParentQ, testUser);
|
||||
STEP("Verify that searching by PARENT and folderR will find node folderD.");
|
||||
SearchRequest queryParentR = req("PARENT:" + folders.get(R).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(queryParentR, testUser,
|
||||
// primary child
|
||||
folderD.getName());
|
||||
|
||||
STEP("Clean-up - delete folderD.");
|
||||
folders.modify(folderD).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that copying folder will also result in copying folder's secondary children and update PARENT index in ES. Test changes below folders hierarchy:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fS += fG += fH
|
||||
* +
|
||||
* /
|
||||
* += fP += file
|
||||
* += fT
|
||||
* </pre>
|
||||
*
|
||||
* into:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fS += fG += fH
|
||||
* +
|
||||
* /
|
||||
* += fP += file
|
||||
* \
|
||||
* +
|
||||
* += fT += fG-c += fH-c
|
||||
* </pre>
|
||||
*/
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryParentWithCopiedSecondaryParentNode()
|
||||
{
|
||||
// given
|
||||
STEP("Create nested folders (G and H) inside folderS and folderT in Document Library. Make folderP a secondary child of folderG.");
|
||||
FolderModel folderG = folders.modify(S).add().randomFolder("G").create();
|
||||
FolderModel folderH = folders.modify(folderG).add().randomFolder("H").create();
|
||||
FolderModel folderT = folders.add().randomFolder("T").create();
|
||||
folders.modify(folderG).add().secondaryContent(folders.get(P));
|
||||
|
||||
STEP("Verify that searching by PARENT and folderG will find nodes: folderH, folderP and file.");
|
||||
SearchRequest queryParentG = req("PARENT:" + folderG.getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(queryParentG, testUser,
|
||||
// primary child
|
||||
folderH.getName(),
|
||||
// secondary child
|
||||
folders.get(P).getName());
|
||||
STEP("Verify that searching by PARENT and folderS will find nodes: folderG, folderH, folderP and file.");
|
||||
SearchRequest queryParentS = req("PARENT:" + folders.get(S).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(queryParentS, testUser,
|
||||
// primary child
|
||||
folderG.getName());
|
||||
STEP("Verify that searching by PARENT and folderP will find node: file.");
|
||||
SearchRequest queryParentP = req("PARENT:" + folders.get(P).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(queryParentP, testUser,
|
||||
// primary child
|
||||
fileInP.getName());
|
||||
|
||||
// when
|
||||
STEP("Copy folderG with its content to folderT.");
|
||||
FolderModel folderGCopy = folders.modify(folderG).copyTo(folderT);
|
||||
|
||||
// then
|
||||
STEP("Verify that search result for PARENT and folderS didn't change.");
|
||||
searchQueryService.expectResultsFromQuery(queryParentS, testUser,
|
||||
// primary child
|
||||
folderG.getName());
|
||||
STEP("Verify that searching by PARENT and folderS/folderG will find nodes: folderH, folderP and file in P.");
|
||||
searchQueryService.expectResultsFromQuery(queryParentG, testUser,
|
||||
// primary child
|
||||
folderH.getName(),
|
||||
// secondary child
|
||||
folders.get(P).getName());
|
||||
STEP("Verify that folderG was copied with secondary parent-child relationship and PARENT reflects that - search by folderT/folderGCopy should find nodes: folderH, folderP and file in P.");
|
||||
SearchRequest queryParentGCopy = req("PARENT:" + folderGCopy.getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(queryParentGCopy, testUser,
|
||||
// primary child
|
||||
folderH.getName(), // name is the same as folderH-copy
|
||||
// secondary child
|
||||
folders.get(P).getName());
|
||||
STEP("Verify that this time searching by PARENT and folderP will find node: file.");
|
||||
searchQueryService.expectResultsFromQuery(queryParentP, testUser,
|
||||
// primary child
|
||||
fileInP.getName());
|
||||
|
||||
STEP("Clean-up - delete folderG and folderT (with G's copy).");
|
||||
folders.modify(folderG).delete();
|
||||
folders.modify(folderT).delete();
|
||||
}
|
||||
}
|
||||
+392
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
|
||||
/**
|
||||
* Tests verifying indexing of secondary children and PATH index in Elasticsearch.
|
||||
*/
|
||||
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"}) // these are TAS E2E tests and use searchQueryService.expectResultsFromQuery for assertion
|
||||
public class NodesSecondaryPathIndexingTests extends NodesSecondaryChildrenRelatedTests
|
||||
{
|
||||
|
||||
private FileModel fileInP;
|
||||
|
||||
/**
|
||||
* Creates a user and a private site containing below hierarchy of folders.
|
||||
*
|
||||
* <pre>
|
||||
* Site
|
||||
* DL (Document Library)
|
||||
* += fA += fB += fC (folderC)
|
||||
* / / |
|
||||
* + + +
|
||||
* += fK += fL += fM
|
||||
* | +
|
||||
* + |
|
||||
* += fX += fY += fZ
|
||||
* += fP += file -+ fA
|
||||
* += fQ
|
||||
* += fR
|
||||
* += fS
|
||||
* </pre>
|
||||
*
|
||||
* Parent += Child - primary parent-child relationship Parent +- Child - secondary parent-child relationship
|
||||
*/
|
||||
@BeforeClass(alwaysRun = true)
|
||||
@Override
|
||||
public void dataPreparation()
|
||||
{
|
||||
super.dataPreparation();
|
||||
|
||||
// given
|
||||
STEP("Create few sets of nested folders in site's Document Library.");
|
||||
folders.add().nestedRandomFolders(A, B, C).create();
|
||||
folders.add().nestedRandomFolders(K, L, M).create();
|
||||
folders.add().nestedRandomFolders(X, Y, Z).create();
|
||||
folders.add().randomFolders(P, Q, R, S).create();
|
||||
fileInP = folders.modify(P).add().randomFile().create();
|
||||
|
||||
STEP("Create few secondary parent-child relationships.");
|
||||
folders.modify(K).add().secondaryContent(folders.get(B));
|
||||
folders.modify(X).add().secondaryContent(folders.get(K));
|
||||
folders.modify(L).add().secondaryContent(folders.get(C), folders.get(Y));
|
||||
folders.modify(M).add().secondaryContent(folders.get(C));
|
||||
folders.modify(A).add().secondaryContent(fileInP);
|
||||
|
||||
STEP("Add to folderQ a secondary child folderR.");
|
||||
folders.modify(Q).add().secondaryContent(folders.get(R));
|
||||
|
||||
// when
|
||||
STEP("Delete the secondary parent relationship between folderQ and FolderR.");
|
||||
folders.modify(Q).remove().secondaryContent(folders.get(R));
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryPathWithNodeHavingOneSecondaryChild()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that folderC can be found by secondary PATH using secondary parent folderM.");
|
||||
SearchRequest query = req("PATH:\"//cm:" + folders.get(M).getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// secondary path
|
||||
folders.get(C).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryPathWithNodeHavingOnePrimaryAndTwoSecondaryChildren()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that primary and secondary children of folderL can be found using PATH index.");
|
||||
SearchRequest query = req("PATH:\"//cm:" + folders.get(L).getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// primary path
|
||||
folders.get(M).getName(),
|
||||
// secondary path
|
||||
folders.get(C).getName(),
|
||||
folders.get(Y).getName(),
|
||||
folders.get(Z).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryPathWithNodeHavingDocumentAsSecondaryChild()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that a file being a secondary child of folderA can be found using PATH index.");
|
||||
SearchRequest query = req("PATH:\"//cm:" + folders.get(A).getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// primary path
|
||||
folders.get(B).getName(),
|
||||
folders.get(C).getName(),
|
||||
// secondary path
|
||||
fileInP.getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryPathWithNodeHavingComplexSecondaryRelationship()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that all secondary children of folderX can be found.");
|
||||
SearchRequest query = req("PATH:\"//cm:" + folders.get(X).getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// primary path
|
||||
folders.get(Y).getName(),
|
||||
folders.get(Z).getName(),
|
||||
// secondary path
|
||||
folders.get(B).getName(),
|
||||
folders.get(C).getName(),
|
||||
folders.get(K).getName(),
|
||||
folders.get(L).getName(),
|
||||
folders.get(M).getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that removing secondary parent-child relationship will result in updating ES index: PATH. Test changes below folders hierarchy:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fQ
|
||||
* +
|
||||
* |
|
||||
* += fR
|
||||
* </pre>
|
||||
*
|
||||
* into:
|
||||
*
|
||||
* <pre>
|
||||
* DL += fQ += fR
|
||||
* </pre>
|
||||
*/
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryPathWithDeletedSecondaryRelationship()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that folderR cannot be found by PATH and folderQ anymore.");
|
||||
SearchRequest query = req("PATH:\"//cm:" + folders.get(Q).getName() + "//*\"");
|
||||
searchQueryService.expectNoResultsFromQuery(query, testUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that removing a node D (fD) having a secondary children relationship will remove the relationships and update PATH index in ES. Test changes below folders hierarchy from:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fQ
|
||||
* +
|
||||
* |
|
||||
* += fE += fF
|
||||
* +
|
||||
* |
|
||||
* += fR
|
||||
* </pre>
|
||||
*
|
||||
* into:
|
||||
*
|
||||
* <pre>
|
||||
* DL += fQ += fR
|
||||
* </pre>
|
||||
*/
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryPathWithDeletedSecondaryParentNode()
|
||||
{
|
||||
// given
|
||||
STEP("Create two nested folders (E and F) in Document Library.");
|
||||
FolderModel folderE = folders.add().randomFolder("E").create();
|
||||
FolderModel folderF = folders.modify(folderE).add().randomFolder("F").create();
|
||||
STEP("Make folderE a secondary children of folderQ and folderR a secondary children of folderE.");
|
||||
folders.modify(Q).add().secondaryContent(folderE);
|
||||
folders.modify(folderE).add().secondaryContent(folders.get(R));
|
||||
|
||||
STEP("Verify that searching by PATH and folderQ will find nodes: folderE, folderF and folderR.");
|
||||
SearchRequest queryPathQ = req("PATH:\"//cm:" + folders.get(Q).getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(queryPathQ, testUser,
|
||||
// secondary path
|
||||
folderE.getName(),
|
||||
folderF.getName(),
|
||||
folders.get(R).getName());
|
||||
STEP("Verify that searching by PATH and folderE will find its primary and secondary children: folderF and folderR.");
|
||||
SearchRequest queryPathD = req("PATH:\"//cm:" + folderE.getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(queryPathD, testUser,
|
||||
// primary path
|
||||
folderF.getName(),
|
||||
// secondary path
|
||||
folders.get(R).getName());
|
||||
|
||||
// when
|
||||
STEP("Delete folderE and verify that PATH was updated for nodes folderQ and folderR.");
|
||||
folders.modify(folderE).delete();
|
||||
|
||||
// then
|
||||
searchQueryService.expectNoResultsFromQuery(queryPathQ, testUser);
|
||||
searchQueryService.expectNoResultsFromQuery(queryPathD, testUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that moving folder D (fD) containing secondary children from hierarchy:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fQ += fD +- fP += file
|
||||
* += fR
|
||||
* </pre>
|
||||
*
|
||||
* to:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fQ
|
||||
* += fR += fD +- fP += file
|
||||
* </pre>
|
||||
*
|
||||
* will update PATH index in ES.
|
||||
*/
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryPathWithMovedSecondaryParentNode()
|
||||
{
|
||||
// given
|
||||
STEP("Create folderD inside folderQ, and add folderP as a secondary child.");
|
||||
FolderModel folderD = folders.modify(Q).add().randomFolder("D").create();
|
||||
folders.modify(folderD).add().secondaryContent(folders.get(P));
|
||||
folders.modify(M).add().secondaryContent(folderD);
|
||||
|
||||
STEP("Verify that searching by PATH and folderD will find nodes: folderP and file.");
|
||||
SearchRequest queryPathD = req("PATH:\"//cm:" + folderD.getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(queryPathD, testUser,
|
||||
// secondary path
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName());
|
||||
STEP("Verify that searching by PATH and folderQ will find nodes: folderD, folderP and file.");
|
||||
SearchRequest queryPathQ = req("PATH:\"//cm:" + folders.get(Q).getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(queryPathQ, testUser,
|
||||
// primary path
|
||||
folderD.getName(),
|
||||
// secondary path
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName());
|
||||
|
||||
// when
|
||||
STEP("Move folderD from folderQ to folderR.");
|
||||
folders.modify(folderD).moveTo(folders.get(R));
|
||||
|
||||
// then
|
||||
STEP("Verify that search result for PATH and folderD didn't change.");
|
||||
searchQueryService.expectResultsFromQuery(queryPathD, testUser,
|
||||
// secondary path
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName());
|
||||
STEP("Verify that searching by PATH and folderQ doesn't return any node anymore.");
|
||||
searchQueryService.expectNoResultsFromQuery(queryPathQ, testUser);
|
||||
STEP("Verify that searching by PATH and folderR will find nodes: folderD, folderP and file.");
|
||||
SearchRequest queryPathR = req("PATH:\"//cm:" + folders.get(R).getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(queryPathR, testUser,
|
||||
// primary path
|
||||
folderD.getName(),
|
||||
// secondary path
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName());
|
||||
|
||||
STEP("Clean-up - delete folderD.");
|
||||
folders.modify(folderD).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that copying folder will also result in copying folder's secondary children and update PATH index in ES. Test changes below folders hierarchy:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fS += fG += fH
|
||||
* +
|
||||
* /
|
||||
* += fP += file
|
||||
* += fT
|
||||
* </pre>
|
||||
*
|
||||
* into:
|
||||
*
|
||||
* <pre>
|
||||
* DL
|
||||
* += fS += fG += fH
|
||||
* +
|
||||
* /
|
||||
* += fP += file
|
||||
* \
|
||||
* +
|
||||
* += fT += fG-c += fH-c
|
||||
* </pre>
|
||||
*/
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryParentWithCopiedSecondaryParentNode()
|
||||
{
|
||||
// given
|
||||
STEP("Create nested folders (G and H) inside folderS and folderT in Document Library. Make folderP a secondary child of folderG.");
|
||||
FolderModel folderG = folders.modify(S).add().randomFolder("G").create();
|
||||
FolderModel folderH = folders.modify(folderG).add().randomFolder("H").create();
|
||||
FolderModel folderT = folders.add().randomFolder("T").create();
|
||||
folders.modify(folderG).add().secondaryContent(folders.get(P));
|
||||
|
||||
STEP("Verify that searching by PATH and folderG will find nodes: folderH, folderP and file.");
|
||||
SearchRequest queryPathG = req("PATH:\"//cm:" + folderG.getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(queryPathG, testUser,
|
||||
// primary path
|
||||
folderH.getName(),
|
||||
// secondary path
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName());
|
||||
STEP("Verify that searching by PATH and folderS will find nodes: folderG, folderH, folderP and file.");
|
||||
SearchRequest queryPathS = req("PATH:\"//cm:" + folders.get(S).getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(queryPathS, testUser,
|
||||
// primary path
|
||||
folderG.getName(),
|
||||
folderH.getName(),
|
||||
// secondary path
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName());
|
||||
|
||||
// when
|
||||
STEP("Copy folderG with its content to folderT.");
|
||||
FolderModel folderGCopy = folders.modify(folderG).copyTo(folderT);
|
||||
|
||||
// then
|
||||
STEP("Verify that search result for PATH and folderS didn't change.");
|
||||
searchQueryService.expectResultsFromQuery(queryPathS, testUser,
|
||||
// primary path
|
||||
folderH.getName(),
|
||||
folderG.getName(),
|
||||
// secondary path
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName());
|
||||
STEP("Verify that searching by PATH and folderS/folderG will find nodes: folderH, folderP and file in P.");
|
||||
SearchRequest queryPathSG = req("PATH:\"//cm:" + folders.get(S).getName() + "/cm:" + folderG.getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(queryPathSG, testUser,
|
||||
// primary path
|
||||
folderH.getName(),
|
||||
// secondary path
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName());
|
||||
STEP("Verify that folderG was copied with secondary parent-child relationship and PATH reflects that - search by folderT/folderGCopy should find nodes: folderH, folderP and file in P.");
|
||||
SearchRequest queryPathTGCopy = req("PATH:\"//cm:" + folderT.getName() + "/cm:" + folderGCopy.getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(queryPathTGCopy, testUser,
|
||||
// primary path
|
||||
folderH.getName(), // the same name as folderH-copy
|
||||
// secondary path
|
||||
folders.get(P).getName(),
|
||||
fileInP.getName());
|
||||
|
||||
STEP("Clean-up - delete folderG and folderT (with G's copy).");
|
||||
folders.modify(folderG).delete();
|
||||
folders.modify(folderT).delete();
|
||||
}
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.utility.Utility;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
|
||||
@SuppressWarnings({"PMD.JUnitTestsShouldIncludeAssert"}) // these are TAS E2E tests and use searchQueryService.expectResultsFromQuery for assertion
|
||||
public class PathFieldsIndexingTests extends NodesSecondaryChildrenRelatedTests
|
||||
{
|
||||
/**
|
||||
* Creates a user and a private site containing below hierarchy of folders.
|
||||
*
|
||||
* <pre>
|
||||
* Site
|
||||
* DL (Document Library)
|
||||
* += fA += fB += fC (folderC)
|
||||
* / / |
|
||||
* + + +
|
||||
* += fK += fL += fM
|
||||
* | +
|
||||
* + |
|
||||
* += fX += fY += fZ
|
||||
* </pre>
|
||||
*
|
||||
* Parent += Child - primary parent-child relationship Parent +- Child - secondary parent-child relationship
|
||||
*/
|
||||
@BeforeClass(alwaysRun = true)
|
||||
@Override
|
||||
public void dataPreparation()
|
||||
{
|
||||
super.dataPreparation();
|
||||
|
||||
// given
|
||||
STEP("Create some nested folders.");
|
||||
folders.add().nestedRandomFolders(A, B, C).create();
|
||||
folders.add().nestedRandomFolders(K, L, M).create();
|
||||
folders.add().nestedRandomFolders(X, Y, Z).create();
|
||||
|
||||
STEP("Add some extra secondary parent relationships.");
|
||||
folders.modify(K).add().secondaryContent(folders.get(B));
|
||||
folders.modify(X).add().secondaryContent(folders.get(K));
|
||||
folders.modify(L).add().secondaryContent(folders.get(C), folders.get(Y));
|
||||
folders.modify(M).add().secondaryContent(folders.get(C));
|
||||
|
||||
STEP("Wait for the batch indexer to index the last secondary association (M +- C).");
|
||||
SearchRequest indexingProbe = req("PARENT:" + folders.get(M).getNodeRef());
|
||||
try
|
||||
{
|
||||
Utility.sleep(500, 60000, () -> searchQueryService.expectResultsFromQuery(
|
||||
indexingProbe, testUser, folders.get(C).getName()));
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
Thread.currentThread().interrupt();
|
||||
throw new AssertionError("Interrupted while waiting for batch indexer", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testPrimaryParentField()
|
||||
{
|
||||
STEP("Check that only M has L as a primary parent.");
|
||||
SearchRequest query = req("PRIMARYPARENT:" + folders.get(L).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
folders.get(M).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testParentFieldIncludesSecondaryParents()
|
||||
{
|
||||
STEP("Check that three nodes have L as a primary or secondary parent.");
|
||||
SearchRequest query = req("PARENT:" + folders.get(L).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
folders.get(C).getName(), folders.get(M).getName(), folders.get(Y).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testAncestorFieldIncludesSecondaryAssociations()
|
||||
{
|
||||
STEP("Check which nodes have K as an ancestor.");
|
||||
SearchRequest query = req("ANCESTOR:" + folders.get(K).getNodeRef());
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
folders.get(B).getName(), folders.get(C).getName(), folders.get(L).getName(), folders.get(M).getName(),
|
||||
folders.get(Y).getName(), folders.get(Z).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryPathWithNodeHavingOneSecondaryChild()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that folderC can be found by secondary PATH using secondary parent folderM.");
|
||||
SearchRequest query = req("PATH:\"//cm:" + folders.get(M).getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// secondary path
|
||||
folders.get(C).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryPathWithNodeHavingOnePrimaryAndTwoSecondaryChildren()
|
||||
{
|
||||
// then
|
||||
STEP("Verify that primary and secondary children of folderL can be found using PATH index.");
|
||||
SearchRequest query = req("PATH:\"//cm:" + folders.get(L).getName() + "//*\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// primary path
|
||||
folders.get(M).getName(),
|
||||
// secondary path
|
||||
folders.get(C).getName(),
|
||||
folders.get(Y).getName(),
|
||||
folders.get(Z).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testQueryThroughSecondaryPath()
|
||||
{
|
||||
// then
|
||||
STEP("Verify we can get direct children of a secondary paths (X-K=L) referenced in a query.");
|
||||
SearchRequest query = req("PATH:\"//cm:" + folders.get(X).getName() + "//cm:" + folders.get(L).getName() + "/*\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// primary path from L
|
||||
folders.get(M).getName(),
|
||||
// secondary path from L
|
||||
folders.get(C).getName(),
|
||||
folders.get(Y).getName());
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void testSecondaryPathWithNodeHavingComplexSecondaryRelationship()
|
||||
{
|
||||
// then
|
||||
STEP("Verify we can find all secondary descendents (excluding direct children) of folderX.");
|
||||
SearchRequest query = req("PATH:\"//cm:" + folders.get(X).getName() + "//*//*\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser,
|
||||
// primary path
|
||||
folders.get(Z).getName(),
|
||||
// secondary path
|
||||
folders.get(Y).getName(), // Y _is_ a direct primary child, but this query should only find it via the secondary path.
|
||||
folders.get(B).getName(),
|
||||
folders.get(C).getName(),
|
||||
folders.get(L).getName(),
|
||||
folders.get(M).getName());
|
||||
}
|
||||
}
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
|
||||
import java.util.Arrays;
|
||||
import jakarta.json.Json;
|
||||
import jakarta.json.JsonObject;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.elasticsearch.utility.ElasticsearchRESTHelper;
|
||||
import org.alfresco.rest.core.RestWrapper;
|
||||
import org.alfresco.rest.model.RestCategoryModel;
|
||||
import org.alfresco.rest.model.RestNodeBodyMoveCopyModel;
|
||||
import org.alfresco.rest.model.RestNodeModel;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.ContentModel;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
import org.alfresco.utility.report.log.Step;
|
||||
|
||||
/**
|
||||
* Tests to check that paths are updated correctly.
|
||||
*/
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializer.class)
|
||||
public class PathUpdateTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
@Autowired
|
||||
protected RestWrapper restClient;
|
||||
@Autowired
|
||||
private ElasticsearchRESTHelper helper;
|
||||
@Autowired
|
||||
protected SearchQueryService searchQueryService;
|
||||
|
||||
private UserModel testUser;
|
||||
private SiteModel testSite;
|
||||
|
||||
/**
|
||||
* Create a user and a private site containing some nested folders with a document in.
|
||||
*/
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
serverHealth.isServerReachable();
|
||||
serverHealth.assertServerIsOnline();
|
||||
|
||||
Step.STEP("Create a test user and private site.");
|
||||
testUser = dataUser.createRandomTestUser();
|
||||
testSite = helper.createPrivateSite(testUser);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeFileNameUpdatesPath()
|
||||
{
|
||||
Step.STEP("Create a file in the site.");
|
||||
FileModel testFile = helper.createFileInSite(testUser, testSite);
|
||||
|
||||
Step.STEP("Update the filename.");
|
||||
RestNodeModel updatedFile = renameNode(testFile);
|
||||
|
||||
Step.STEP("Check the path is updated");
|
||||
SearchRequest query = req("PATH:\"" + pathInSite(testSite, updatedFile.getName()) + "\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, updatedFile.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeFileParentUpdatesPath()
|
||||
{
|
||||
Step.STEP("Create a file next to a folder.");
|
||||
FileModel testFile = helper.createFileInSite(testUser, testSite);
|
||||
FolderModel testFolder = helper.createFolderInSite(testUser, testSite);
|
||||
|
||||
Step.STEP("Move the file into the folder and check the path is updated.");
|
||||
moveNode(testFile, testFolder);
|
||||
|
||||
Step.STEP("Check the path is updated");
|
||||
SearchRequest query = req("PATH:\"" + pathInSite(testSite, testFolder.getName(), testFile.getName()) + "\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeFolderNameUpdatesPath()
|
||||
{
|
||||
Step.STEP("Create a file in a folder.");
|
||||
FolderModel testFolder = helper.createFolderInSite(testUser, testSite);
|
||||
FileModel testFile = helper.createFileInFolder(testUser, testFolder);
|
||||
|
||||
Step.STEP("Update the folder's name.");
|
||||
RestNodeModel updatedFolder = renameNode(testFolder);
|
||||
|
||||
Step.STEP("Check the path is updated");
|
||||
SearchRequest query = req("PATH:\"" + pathInSite(testSite, updatedFolder.getName(), testFile.getName()) + "\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeFolderParentUpdatesPath()
|
||||
{
|
||||
Step.STEP("Create two folders with a file in the first.");
|
||||
FolderModel firstFolder = helper.createFolderInSite(testUser, testSite);
|
||||
FolderModel secondFolder = helper.createFolderInSite(testUser, testSite);
|
||||
FileModel testFile = helper.createFileInFolder(testUser, firstFolder);
|
||||
|
||||
Step.STEP("Move the first folder into the second.");
|
||||
moveNode(firstFolder, secondFolder);
|
||||
|
||||
Step.STEP("Check the path is updated");
|
||||
SearchRequest query = req("PATH:\"" + pathInSite(testSite, secondFolder.getName(), firstFolder.getName(), testFile.getName()) + "\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeCategoriesUpdatesPath()
|
||||
{
|
||||
Step.STEP("Create two categories and a file in the first.");
|
||||
RestCategoryModel firstCategory = helper.createCategory();
|
||||
RestCategoryModel secondCategory = helper.createCategory();
|
||||
FileModel testFile = helper.createFileInSite(testUser, testSite);
|
||||
helper.linkToCategory(testUser, testFile, firstCategory);
|
||||
|
||||
Step.STEP("Remove the file from the first category and add it to the second.");
|
||||
helper.unlinkFromCategory(testUser, testFile, firstCategory);
|
||||
helper.linkToCategory(testUser, testFile, secondCategory);
|
||||
|
||||
Step.STEP("Check there is no path for the first category.");
|
||||
SearchRequest query = req("PATH:\"" + categoryPath(firstCategory.getName(), testFile.getName()) + "\"");
|
||||
searchQueryService.expectNoResultsFromQuery(query, testUser);
|
||||
|
||||
Step.STEP("Check there is a path for the second category.");
|
||||
query = req("PATH:\"" + categoryPath(secondCategory.getName(), testFile.getName()) + "\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeCategoryNameUpdatesPath()
|
||||
{
|
||||
Step.STEP("Create a category and a file in it.");
|
||||
RestCategoryModel category = helper.createCategory();
|
||||
FileModel testFile = helper.createFileInSite(testUser, testSite);
|
||||
helper.linkToCategory(testUser, testFile, category);
|
||||
|
||||
Step.STEP("Update the name of the category.");
|
||||
String newCategoryName = category.getName() + "_updated";
|
||||
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
|
||||
.usingCategory(category)
|
||||
.updateCategory(RestCategoryModel.builder().name(newCategoryName).create());
|
||||
|
||||
Step.STEP("Check there is a path with the updated category name.");
|
||||
SearchRequest query = req("PATH:\"" + categoryPath(newCategoryName, testFile.getName()) + "\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeCategoryPathUpdatesPath()
|
||||
{
|
||||
Step.STEP("Create two nested categories and assign a file to the child.");
|
||||
RestCategoryModel parentCategory = helper.createCategory();
|
||||
RestCategoryModel childCategory = helper.createCategory(parentCategory);
|
||||
FileModel testFile = helper.createFileInSite(testUser, testSite);
|
||||
helper.linkToCategory(testUser, testFile, parentCategory, childCategory);
|
||||
|
||||
Step.STEP("Update the parent category name and check the file's paths are updated.");
|
||||
String newCategoryName = parentCategory.getName() + "_updated";
|
||||
restClient.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
|
||||
.usingCategory(parentCategory)
|
||||
.updateCategory(RestCategoryModel.builder().name(newCategoryName).create());
|
||||
|
||||
Step.STEP("Check there is a path with the updated category name.");
|
||||
SearchRequest query = req("PATH:\"" + categoryPath(newCategoryName, childCategory.getName(), testFile.getName()) + "\"");
|
||||
searchQueryService.expectResultsFromQuery(query, testUser, testFile.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename the specified node to have "_updated" on the end.
|
||||
*
|
||||
* @param node
|
||||
* The node to update.
|
||||
* @return The updated node.
|
||||
*/
|
||||
private RestNodeModel renameNode(ContentModel node)
|
||||
{
|
||||
String newName = node.getName() + "_updated";
|
||||
JsonObject renameJson = Json.createObjectBuilder().add("properties",
|
||||
Json.createObjectBuilder().add("cm:name", newName)).build();
|
||||
return restClient.authenticateUser(testUser).withCoreAPI().usingNode(node).updateNode(renameJson.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the specified node to a folder.
|
||||
*
|
||||
* @param node
|
||||
* The node to move.
|
||||
* @param targetFolder
|
||||
* The folder to move the node to.
|
||||
* @return The updated node.
|
||||
*/
|
||||
private RestNodeModel moveNode(ContentModel node, FolderModel targetFolder)
|
||||
{
|
||||
RestNodeBodyMoveCopyModel moveBody = new RestNodeBodyMoveCopyModel();
|
||||
moveBody.setTargetParentId(targetFolder.getNodeRef());
|
||||
return restClient.authenticateUser(testUser).withCoreAPI().usingNode(node).move(moveBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a path to a file or folder in a site.
|
||||
*
|
||||
* @param site
|
||||
* The site object.
|
||||
* @param documentLibraryNames
|
||||
* The list of names of nodes from the document library to the target file or folder.
|
||||
* @return An absolute path suitable for use in a path query.
|
||||
*/
|
||||
private String pathInSite(SiteModel site, String... documentLibraryNames)
|
||||
{
|
||||
return "/app:company_home/st:sites/cm:" + site.getId() + "/cm:documentLibrary/cm:" + stream(documentLibraryNames).collect(joining("/cm:"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a path to a file or folder via the category hierarchy.
|
||||
*
|
||||
* @param nodeNames
|
||||
* The ordered list of node names from the root category to the node that was categorised.
|
||||
* @return An absolute path through the categories to the specified node.
|
||||
*/
|
||||
private String categoryPath(String... nodeNames)
|
||||
{
|
||||
return "/cm:categoryRoot/cm:generalclassifiable/cm:" + Arrays.stream(nodeNames).collect(joining("/cm:"));
|
||||
}
|
||||
}
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertFalse;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import org.alfresco.rest.core.RestWrapper;
|
||||
import org.alfresco.rest.search.RestRequestQueryModel;
|
||||
import org.alfresco.rest.search.RestRequestTemplatesModel;
|
||||
import org.alfresco.rest.search.SearchNodeModel;
|
||||
import org.alfresco.rest.search.SearchRequest;
|
||||
import org.alfresco.rest.search.SearchResponse;
|
||||
import org.alfresco.utility.Utility;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
|
||||
/** A class providing methods for testing search queries. */
|
||||
public class SearchQueryService
|
||||
{
|
||||
/** Maximum time to allow for search query to return correct results. */
|
||||
private static final int MAX_TIME = 10000;
|
||||
@Autowired
|
||||
private RestWrapper client;
|
||||
|
||||
/** Assert that the query returns no results. */
|
||||
public void expectNoResultsFromQuery(SearchRequest searchRequest, UserModel testUser)
|
||||
{
|
||||
expectResultsFromQuery(searchRequest, testUser);
|
||||
}
|
||||
|
||||
/** Assert that the query returns something, without checking exactly what it returns. */
|
||||
public void expectSomeResultsFromQuery(SearchRequest searchRequest, UserModel testUser)
|
||||
{
|
||||
Consumer<SearchResponse> assertNotEmpty = searchResponse -> assertFalse(searchResponse.isEmpty());
|
||||
expectResultsFromQuery(searchRequest, testUser, assertNotEmpty);
|
||||
}
|
||||
|
||||
public void expectResultsInOrder(SearchRequest searchRequest, UserModel user, boolean isAscending, String... expected)
|
||||
{
|
||||
Consumer<SearchResponse> response = searchResponse -> assertNamesInOrder(searchResponse, isAscending, expected);
|
||||
expectResultsFromQuery(searchRequest, user, response);
|
||||
}
|
||||
|
||||
public void expectResultsInOrder(SearchRequest searchRequest, UserModel user, String... expectedOrder)
|
||||
{
|
||||
Consumer<SearchResponse> response = searchResponse -> assertNamesInOrder(searchResponse, expectedOrder);
|
||||
expectResultsFromQuery(searchRequest, user, response);
|
||||
}
|
||||
|
||||
public void expectResultsStartingWithOneOf(SearchRequest searchRequest, UserModel user, String... expected)
|
||||
{
|
||||
Consumer<SearchResponse> response = searchResponse -> {
|
||||
List<String> expectedFirstElements = List.of(expected);
|
||||
String actualFirstElement = searchResponse.getEntries().stream()
|
||||
.map(SearchNodeModel::getModel)
|
||||
.map(SearchNodeModel::getName)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
assertTrue(expectedFirstElements.contains(actualFirstElement), "Unexpected search results - actual first element: " + actualFirstElement + ", expected one of: " + expectedFirstElements + " |");
|
||||
};
|
||||
expectResultsFromQuery(searchRequest, user, response);
|
||||
}
|
||||
|
||||
public void expectResultsFromQuery(SearchRequest searchRequest, UserModel user, String... expected)
|
||||
{
|
||||
Consumer<SearchResponse> assertNames = searchResponse -> assertNames(searchResponse, expected);
|
||||
expectResultsFromQuery(searchRequest, user, assertNames);
|
||||
}
|
||||
|
||||
/** Check that the specified results are all included in the result set. */
|
||||
public void expectResultsInclude(SearchRequest searchRequest, UserModel user, String... expected)
|
||||
{
|
||||
Consumer<SearchResponse> assertNames = searchResponse -> assertNamesInclude(searchResponse, expected);
|
||||
expectResultsFromQuery(searchRequest, user, assertNames);
|
||||
}
|
||||
|
||||
public void expectNodeTypesFromQuery(SearchRequest searchRequest, UserModel user, String... expected)
|
||||
{
|
||||
Consumer<SearchResponse> assertNodeTypes = searchResponse -> assertNodeTypes(searchResponse, expected);
|
||||
expectResultsFromQuery(searchRequest, user, assertNodeTypes);
|
||||
}
|
||||
|
||||
public void expectNodeRefsFromQuery(SearchRequest searchRequest, UserModel user, String... expectedNodeRefs)
|
||||
{
|
||||
Consumer<SearchResponse> assertNames = searchResponse -> assertNodeRefs(searchResponse, expectedNodeRefs);
|
||||
expectResultsFromQuery(searchRequest, user, assertNames);
|
||||
}
|
||||
|
||||
public void expectAllResultsFromQuery(SearchRequest searchRequest, UserModel user, Predicate<SearchNodeModel> assertionMethod)
|
||||
{
|
||||
Function<SearchNodeModel, String> failureMessage = searchNodeModel -> "'" + searchNodeModel.getName() + "' did not satisfy predicate.";
|
||||
expectAllResultsFromQuery(searchRequest, user, assertionMethod, failureMessage);
|
||||
}
|
||||
|
||||
public void expectAllResultsFromQuery(SearchRequest searchRequest, UserModel user, Predicate<SearchNodeModel> assertionMethod, Function<SearchNodeModel, String> failureMessageFunction)
|
||||
{
|
||||
expectResultsFromQuery(searchRequest, user, searchResponse -> assertAllSearchResults(searchResponse, assertionMethod, failureMessageFunction));
|
||||
}
|
||||
|
||||
public void expectTotalHitsFromQuery(SearchRequest searchRequest, UserModel user, int expected)
|
||||
{
|
||||
expectResultsFromQuery(searchRequest, user, searchResponse -> assertTotalHitsResults(searchResponse, expected));
|
||||
}
|
||||
|
||||
private void expectResultsFromQuery(SearchRequest searchRequest, UserModel user, Consumer<SearchResponse> assertionMethod)
|
||||
{
|
||||
try
|
||||
{
|
||||
Utility.sleep(1000, MAX_TIME, () -> {
|
||||
SearchResponse response = client.authenticateUser(user)
|
||||
.withSearchAPI()
|
||||
.search(searchRequest);
|
||||
client.assertStatusCodeIs(HttpStatus.OK);
|
||||
assertionMethod.accept(response);
|
||||
});
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
fail("InterruptedException received while waiting for results.");
|
||||
}
|
||||
}
|
||||
|
||||
public void expectErrorFromQuery(SearchRequest searchRequest, org.alfresco.utility.model.UserModel user,
|
||||
HttpStatus expectedStatusCode, String containsErrorString)
|
||||
{
|
||||
client.authenticateUser(user).withSearchAPI().search(searchRequest);
|
||||
client.assertStatusCodeIs(expectedStatusCode);
|
||||
client.assertLastError().containsSummary(containsErrorString);
|
||||
}
|
||||
|
||||
private void assertNodeRefs(SearchResponse actual, String... expected)
|
||||
{
|
||||
Set<String> result = actual.getEntries().stream()
|
||||
.map(SearchNodeModel::getModel)
|
||||
.map(SearchNodeModel::getId)
|
||||
.collect(Collectors.toSet());
|
||||
Set<String> expectedList = Sets.newHashSet(expected);
|
||||
assertEquals(result, expectedList, "Unexpected search results - got " + result + " expected " + expectedList);
|
||||
}
|
||||
|
||||
private void assertNames(SearchResponse actual, String... expected)
|
||||
{
|
||||
Set<String> result = actual.getEntries().stream()
|
||||
.map(SearchNodeModel::getModel)
|
||||
.map(SearchNodeModel::getName)
|
||||
.collect(Collectors.toSet());
|
||||
Set<String> expectedList = Sets.newHashSet(expected);
|
||||
assertEquals(result, expectedList, "Unexpected search results - got " + result + " expected " + expectedList);
|
||||
}
|
||||
|
||||
/** Check that the given names are included in the result set. */
|
||||
private void assertNamesInclude(SearchResponse actual, String... expected)
|
||||
{
|
||||
Set<String> expectedSet = Sets.newHashSet(expected);
|
||||
// Filter to only include results that were expected.
|
||||
Set<String> filteredResults = actual.getEntries().stream()
|
||||
.map(SearchNodeModel::getModel)
|
||||
.map(SearchNodeModel::getName)
|
||||
.filter(name -> expectedSet.contains(name))
|
||||
.collect(Collectors.toSet());
|
||||
assertEquals(filteredResults, expectedSet, "Did not receive all the expected search results - got " + filteredResults + " from expected set " + expectedSet);
|
||||
}
|
||||
|
||||
private List<String> orderNames(boolean isAscending, String... filename)
|
||||
{
|
||||
List<String> orderedNames = Arrays.stream(filename).sorted().collect(Collectors.toList());
|
||||
if (!isAscending)
|
||||
{
|
||||
orderedNames = orderedNames.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
|
||||
}
|
||||
return orderedNames;
|
||||
}
|
||||
|
||||
private void assertNamesInOrder(SearchResponse actual, boolean isAscending, String... expected)
|
||||
{
|
||||
List<String> expectedInOrder = orderNames(isAscending, expected);
|
||||
List<String> result = actual.getEntries().stream()
|
||||
.map(SearchNodeModel::getModel)
|
||||
.map(SearchNodeModel::getName)
|
||||
.collect(Collectors.toList());
|
||||
assertEquals(result, expectedInOrder, "Unexpected search results - got " + result + " expected " + expectedInOrder);
|
||||
}
|
||||
|
||||
private void assertNamesInOrder(SearchResponse actual, String... expectedOrder)
|
||||
{
|
||||
List<String> expectedInOrder = List.of(expectedOrder);
|
||||
List<String> result = actual.getEntries().stream()
|
||||
.map(SearchNodeModel::getModel)
|
||||
.map(SearchNodeModel::getName)
|
||||
.toList();
|
||||
assertEquals(result, expectedInOrder, "Unexpected search results - got " + result + " expected " + expectedInOrder);
|
||||
}
|
||||
|
||||
private void assertNodeTypes(SearchResponse actual, String... expected)
|
||||
{
|
||||
Set<String> result = actual.getEntries().stream()
|
||||
.map(SearchNodeModel::getModel)
|
||||
.map(SearchNodeModel::getNodeType)
|
||||
.collect(Collectors.toSet());
|
||||
Set<String> expectedList = Sets.newHashSet(expected);
|
||||
assertEquals(result, expectedList, "Unexpected search results - got " + result + " expected " + expectedList);
|
||||
}
|
||||
|
||||
private void assertAllSearchResults(SearchResponse actual, Predicate<SearchNodeModel> assertion, Function<SearchNodeModel, String> failureMessageFunction)
|
||||
{
|
||||
String result = actual.getEntries().stream()
|
||||
.map(SearchNodeModel::getModel)
|
||||
.filter(Predicate.not(assertion))
|
||||
.map(failureMessageFunction)
|
||||
.collect(Collectors.joining("\n"));
|
||||
assertTrue(result.isEmpty(), "assertAllSearchResults failed with these issues:\n" + result);
|
||||
}
|
||||
|
||||
private void assertTotalHitsResults(SearchResponse actual, int expected)
|
||||
{
|
||||
int totalItems = actual.getPagination().getTotalItems();
|
||||
assertEquals(totalItems, expected, "Unexpected totalItems results - got " + totalItems + " expected " + expected);
|
||||
}
|
||||
|
||||
public static SearchRequest req(String query)
|
||||
{
|
||||
return req(null, query);
|
||||
}
|
||||
|
||||
public static SearchRequest req(String language, String query)
|
||||
{
|
||||
RestRequestQueryModel restRequestQueryModel = new RestRequestQueryModel();
|
||||
restRequestQueryModel.setQuery(query);
|
||||
Optional.ofNullable(language).ifPresent(restRequestQueryModel::setLanguage);
|
||||
return new SearchRequest(restRequestQueryModel);
|
||||
}
|
||||
|
||||
public static SearchRequest req(String language, String query, Map<String, String> templates)
|
||||
{
|
||||
RestRequestQueryModel restRequestQueryModel = new RestRequestQueryModel();
|
||||
restRequestQueryModel.setQuery(query);
|
||||
Optional.ofNullable(language).ifPresent(restRequestQueryModel::setLanguage);
|
||||
SearchRequest request = new SearchRequest(restRequestQueryModel);
|
||||
List<RestRequestTemplatesModel> templatesModels = templates.entrySet().stream()
|
||||
.map(entry -> RestRequestTemplatesModel.builder().name(entry.getKey()).template(entry.getValue()).create())
|
||||
.toList();
|
||||
request.setTemplates(templatesModels);
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch.basicAuth;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
|
||||
import org.alfresco.tas.AlfrescoStackInitializer;
|
||||
import org.alfresco.tas.SearchEngineType;
|
||||
|
||||
/**
|
||||
* ACS Stack Docker Compose initializer with Basic Authentication for Search Engine service (Opensearch or Elasticsearch).
|
||||
*/
|
||||
public class AlfrescoStackInitializerESBasicAuth extends AlfrescoStackInitializer
|
||||
{
|
||||
|
||||
// Default Elasticsearch credentials
|
||||
private static final String SEARCH_ENGINE_USERNAME = "elastic";
|
||||
private static final String SEARCH_ENGINE_PASSWORD = "bob123"; // pragma: allowlist secret
|
||||
private static final String OPENSEARCH_TEST_ROLE = "test_role";
|
||||
|
||||
@Override
|
||||
public void configureSecuritySettings(GenericContainer searchEngineContainer)
|
||||
{
|
||||
SearchEngineType usedEngine = getImagesConfig().getSearchEngineType();
|
||||
|
||||
if (SearchEngineType.OPENSEARCH_ENGINE.equals(usedEngine))
|
||||
{
|
||||
configureNewUser(searchEngineContainer);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureNewUser(GenericContainer opensearchContainer)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Using password hash for setting up test only
|
||||
String passwordHash = hashPassword(opensearchContainer, SEARCH_ENGINE_PASSWORD);
|
||||
|
||||
addNewUser(opensearchContainer, SEARCH_ENGINE_USERNAME, passwordHash);
|
||||
addNewRole(opensearchContainer, OPENSEARCH_TEST_ROLE, CUSTOM_ALFRESCO_INDEX);
|
||||
addNewRoleMapping(opensearchContainer, OPENSEARCH_TEST_ROLE, SEARCH_ENGINE_USERNAME);
|
||||
applyNewSecurityConfigs(opensearchContainer);
|
||||
}
|
||||
catch (IOException | InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private String hashPassword(GenericContainer opensearchContainer, String password) throws IOException, InterruptedException
|
||||
{
|
||||
return opensearchContainer.execInContainer("/usr/share/opensearch/plugins/opensearch-security/tools/hash.sh", "-p", password)
|
||||
.getStdout().strip();
|
||||
}
|
||||
|
||||
private void applyNewSecurityConfigs(GenericContainer opensearchContainer) throws IOException, InterruptedException
|
||||
{
|
||||
opensearchContainer.execInContainer("sh", "-c", "/usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh " +
|
||||
"-cd /usr/share/opensearch/config/opensearch-security " +
|
||||
"-icl -nhnv " +
|
||||
"-cert /usr/share/opensearch/config/kirk.pem " +
|
||||
"-cacert /usr/share/opensearch/config/root-ca.pem " +
|
||||
"-key /usr/share/opensearch/config/kirk-key.pem");
|
||||
}
|
||||
|
||||
private void addNewRoleMapping(GenericContainer opensearchContainer, String role, String username) throws IOException, InterruptedException
|
||||
{
|
||||
opensearchContainer.execInContainer(
|
||||
"sh", "-c", "echo '\n\n" + newOpensearchRoleMapping(role, username) + "' >> /usr/share/opensearch/config/opensearch-security/roles_mapping.yml");
|
||||
}
|
||||
|
||||
private void addNewRole(GenericContainer opensearchContainer, String role, String index) throws IOException, InterruptedException
|
||||
{
|
||||
opensearchContainer.execInContainer(
|
||||
"sh", "-c", "echo '\n\n" + newOpensearchRule(role, index) + "' >> /usr/share/opensearch/config/opensearch-security/roles.yml");
|
||||
}
|
||||
|
||||
private void addNewUser(GenericContainer opensearchContainer, String username, String passwordHash) throws IOException, InterruptedException
|
||||
{
|
||||
opensearchContainer.execInContainer(
|
||||
"sh", "-c", "echo '\n\n" + newOpensearchUser(username, passwordHash) + "' >> /usr/share/opensearch/config/opensearch-security/internal_users.yml");
|
||||
}
|
||||
|
||||
private String newOpensearchUser(String username, String passwordHash)
|
||||
{
|
||||
return username + ":\n" +
|
||||
" hash: \"" + passwordHash + "\"\n" +
|
||||
" reserved: false\n" +
|
||||
" backend_roles:\n" +
|
||||
" - \"all_access\"\n" +
|
||||
" description: \"New user for testing purposes\"";
|
||||
}
|
||||
|
||||
private String newOpensearchRule(String role, String indexName)
|
||||
{
|
||||
return role + ":\n" +
|
||||
" cluster_permissions:\n" +
|
||||
" - cluster_all\n" +
|
||||
" index_permissions:\n" +
|
||||
" - index_patterns:\n" +
|
||||
" - \"" + indexName + "\"\n" +
|
||||
" - \"alfresco-reindex-state\"\n" +
|
||||
" - \"alfresco-reindex-dead-letter\"\n" +
|
||||
" allowed_actions:\n" +
|
||||
" - \"*\"\n";
|
||||
}
|
||||
|
||||
private String newOpensearchRoleMapping(String role, String user)
|
||||
{
|
||||
return role + ":\n" +
|
||||
" reserved: true\n" +
|
||||
" users:\n" +
|
||||
" - \"" + user + "\"";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GenericContainer createBatchIndexingContainer()
|
||||
{
|
||||
GenericContainer container = super.createBatchIndexingContainer();
|
||||
container.withEnv("SPRING_ELASTICSEARCH_REST_USERNAME", SEARCH_ENGINE_USERNAME);
|
||||
container.withEnv("SPRING_ELASTICSEARCH_REST_PASSWORD", SEARCH_ENGINE_PASSWORD);
|
||||
container.withEnv("ALFRESCO_REINDEX_CONTINUOUS_ALFRESCOINDEXNAME", CUSTOM_ALFRESCO_INDEX);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GenericContainer createSearchEngineContainer()
|
||||
{
|
||||
SearchEngineType usedEngine = getImagesConfig().getSearchEngineType();
|
||||
|
||||
if (SearchEngineType.OPENSEARCH_ENGINE.equals(usedEngine))
|
||||
{
|
||||
return super.createOpensearchContainer()
|
||||
.withEnv("plugins.security.disabled", "false")
|
||||
.withEnv("plugins.security.ssl.http.enabled", "false");
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.createElasticContainer()
|
||||
.withEnv("xpack.security.enabled", "true")
|
||||
.withEnv("ELASTIC_PASSWORD", SEARCH_ENGINE_PASSWORD);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GenericContainer createAlfrescoContainer()
|
||||
{
|
||||
GenericContainer container = super.createAlfrescoContainer();
|
||||
String javaOpts = (String) container.getEnvMap().get("JAVA_OPTS");
|
||||
javaOpts = javaOpts + " -Delasticsearch.user=" + SEARCH_ENGINE_USERNAME + " " +
|
||||
"-Delasticsearch.password=" + SEARCH_ENGINE_PASSWORD;
|
||||
container.getEnvMap().put("JAVA_OPTS", javaOpts);
|
||||
return container;
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch.basicAuth;
|
||||
|
||||
import static org.alfresco.elasticsearch.SearchQueryService.req;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.elasticsearch.SearchQueryService;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FileType;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.network.ServerHealth;
|
||||
|
||||
/**
|
||||
* Basic test for Elasticsearch server with Basic Authentication. The aim of this class is to test that Basic Authentication is working as expected.
|
||||
*/
|
||||
@ContextConfiguration(locations = "classpath:alfresco-elasticsearch-context.xml",
|
||||
initializers = AlfrescoStackInitializerESBasicAuth.class)
|
||||
public class ElasticsearchBasicAuthTests extends AbstractTestNGSpringContextTests
|
||||
{
|
||||
private static final String FILE_0_NAME = "test.txt";
|
||||
|
||||
@Autowired
|
||||
private ServerHealth serverHealth;
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
@Autowired
|
||||
private DataContent dataContent;
|
||||
@Autowired
|
||||
private DataSite dataSite;
|
||||
@Autowired
|
||||
private SearchQueryService searchQueryService;
|
||||
|
||||
private UserModel userSite1;
|
||||
private SiteModel siteModel1;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
serverHealth.assertServerIsOnline();
|
||||
userSite1 = dataUser.createRandomTestUser();
|
||||
siteModel1 = dataSite.usingUser(userSite1).createPrivateRandomSite();
|
||||
createContent(FILE_0_NAME, "This is the first test", siteModel1, userSite1);
|
||||
}
|
||||
|
||||
private FileModel createContent(String filename, String content, SiteModel site, UserModel user)
|
||||
{
|
||||
FileModel fileModel = new FileModel(filename, FileType.TEXT_PLAIN, content);
|
||||
return dataContent.usingUser(user).usingSite(site)
|
||||
.createContent(fileModel);
|
||||
}
|
||||
|
||||
@Test(groups = TestGroup.SEARCH)
|
||||
public void searchCanFindAFile()
|
||||
{
|
||||
searchQueryService.expectResultsFromQuery(req("first AND SITE:\"" + siteModel1.getId() + "\""), userSite1, FILE_0_NAME);
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch.retry;
|
||||
|
||||
import java.util.ConcurrentModificationException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testng.IRetryAnalyzer;
|
||||
import org.testng.ITestResult;
|
||||
|
||||
public class RetryAnalyzer implements IRetryAnalyzer
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RetryAnalyzer.class);
|
||||
private static final int RETRY_LIMIT = 3;
|
||||
private int retryNumber = 0;
|
||||
|
||||
@Override
|
||||
public boolean retry(ITestResult testResult)
|
||||
{
|
||||
retryNumber++;
|
||||
Throwable throwable = testResult.getThrowable();
|
||||
if (retryNumber == RETRY_LIMIT)
|
||||
{
|
||||
LOGGER.info("Retry limit reached: {}, shouldRetry: {}", retryNumber, false, throwable);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
boolean shouldRetry = throwable != null
|
||||
&& (throwable instanceof IllegalStateException && throwable.getMessage() != null
|
||||
&& throwable.getMessage().contains("connection still allocated"))
|
||||
|| (throwable instanceof ConcurrentModificationException)
|
||||
|| (throwable instanceof AssertionError && throwable.getMessage() != null
|
||||
&& throwable.getMessage().contains("Maximum retry period reached"));
|
||||
LOGGER.info("Retry: {}, shouldRetry: {}", retryNumber, shouldRetry, throwable);
|
||||
return shouldRetry;
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch.retry;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.testng.IAnnotationTransformer;
|
||||
import org.testng.annotations.ITestAnnotation;
|
||||
|
||||
/**
|
||||
* Add the {@link RetryAnalyzer} to each test.
|
||||
*/
|
||||
public class RetryAnnotationTransformer implements IAnnotationTransformer
|
||||
{
|
||||
@Override
|
||||
public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod)
|
||||
{
|
||||
annotation.setRetryAnalyzer(RetryAnalyzer.class);
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Tas Elasticsearch
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.elasticsearch.utility;
|
||||
|
||||
import static org.alfresco.utility.model.FileType.TEXT_PLAIN;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import org.alfresco.rest.core.RestWrapper;
|
||||
import org.alfresco.rest.model.RestCategoryLinkBodyModel;
|
||||
import org.alfresco.rest.model.RestCategoryModel;
|
||||
import org.alfresco.utility.data.DataContent;
|
||||
import org.alfresco.utility.data.DataSite;
|
||||
import org.alfresco.utility.data.DataUser;
|
||||
import org.alfresco.utility.data.RandomData;
|
||||
import org.alfresco.utility.model.ContentModel;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
|
||||
/** Helper methods for Elasticsearch E2E tests. */
|
||||
public class ElasticsearchRESTHelper
|
||||
{
|
||||
/** The alias for the root of the category hierarchy. */
|
||||
public static final String ROOT_CATEGORY_ALIAS = "-root-";
|
||||
/** The root of the category hierarchy. */
|
||||
private static final RestCategoryModel ROOT_CATEGORY = RestCategoryModel.builder().id(ROOT_CATEGORY_ALIAS).name(ROOT_CATEGORY_ALIAS).create();
|
||||
|
||||
@Autowired
|
||||
private RestWrapper client;
|
||||
@Autowired
|
||||
private DataSite dataSite;
|
||||
@Autowired
|
||||
private DataContent dataContent;
|
||||
@Autowired
|
||||
private DataUser dataUser;
|
||||
|
||||
/**
|
||||
* Create a private site.
|
||||
*
|
||||
* @param user
|
||||
* The user to use.
|
||||
* @return The new site.
|
||||
*/
|
||||
public SiteModel createPrivateSite(UserModel user)
|
||||
{
|
||||
return dataSite.usingUser(user).createPrivateRandomSite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a folder in a site.
|
||||
*
|
||||
* @param user
|
||||
* The user to use.
|
||||
* @param site
|
||||
* The site to create the folder in.
|
||||
* @return The new folder.
|
||||
*/
|
||||
public FolderModel createFolderInSite(UserModel user, SiteModel site)
|
||||
{
|
||||
return dataContent.usingUser(user).usingSite(site).createFolder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a text file in a site.
|
||||
*
|
||||
* @param user
|
||||
* The user to use.
|
||||
* @param site
|
||||
* The site to create the file in.
|
||||
* @return The new file.
|
||||
*/
|
||||
public FileModel createFileInSite(UserModel user, SiteModel site)
|
||||
{
|
||||
FileModel fileModel = FileModel.getRandomFileModel(TEXT_PLAIN);
|
||||
return dataContent.usingUser(user).usingSite(site).createContent(fileModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file in a folder.
|
||||
*
|
||||
* @param user
|
||||
* The user to use.
|
||||
* @param folder
|
||||
* The folder to create the file in.
|
||||
* @return The new file.
|
||||
*/
|
||||
public FileModel createFileInFolder(UserModel user, FolderModel folder)
|
||||
{
|
||||
FileModel fileModel = FileModel.getRandomFileModel(TEXT_PLAIN);
|
||||
return dataContent.usingUser(user).usingResource(folder).createContent(fileModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a category.
|
||||
*
|
||||
* @param ancestorCategories
|
||||
* The path of categories between the root and the new category, or leave blank to create the category at the "-root-".
|
||||
* @return The newly created category.
|
||||
*/
|
||||
public RestCategoryModel createCategory(RestCategoryModel... ancestorCategories)
|
||||
{
|
||||
RestCategoryModel parent = (ancestorCategories.length > 0 ? ancestorCategories[ancestorCategories.length - 1] : ROOT_CATEGORY);
|
||||
return client.authenticateUser(dataUser.getAdminUser()).withCoreAPI()
|
||||
.usingCategory(parent)
|
||||
.createSingleCategory(RestCategoryModel.builder().name(RandomData.getRandomAlphanumeric()).create());
|
||||
}
|
||||
|
||||
/**
|
||||
* Link a file or folder to a category.
|
||||
*
|
||||
* @param user
|
||||
* The user who should create the link.
|
||||
* @param node
|
||||
* The file or folder to be linked.
|
||||
* @param categoryHierarchy
|
||||
* The full list of categories from the root (excluding "-root-") to the category to use.
|
||||
* @return The category that was linked to.
|
||||
*/
|
||||
public RestCategoryModel linkToCategory(UserModel user, ContentModel node, RestCategoryModel... categoryHierarchy)
|
||||
{
|
||||
RestCategoryModel linkedToCategory = (categoryHierarchy.length > 0 ? categoryHierarchy[categoryHierarchy.length - 1] : ROOT_CATEGORY);
|
||||
return client.authenticateUser(user).withCoreAPI().usingNode(node)
|
||||
.linkToCategory(RestCategoryLinkBodyModel.builder().categoryId(linkedToCategory.getId()).create());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink a node from a category.
|
||||
*
|
||||
* @param user
|
||||
* The user who should remove the link.
|
||||
* @param node
|
||||
* The node to unlink.
|
||||
* @param categoryHierarchy
|
||||
* The full list of categories from the root (excluding "-root-") to the category to use.
|
||||
*/
|
||||
public void unlinkFromCategory(UserModel user, ContentModel node, RestCategoryModel... categoryHierarchy)
|
||||
{
|
||||
RestCategoryModel linkedToCategory = (categoryHierarchy.length > 0 ? categoryHierarchy[categoryHierarchy.length - 1] : ROOT_CATEGORY);
|
||||
client.authenticateUser(user).withCoreAPI().usingNode(node).unlinkFromCategory(linkedToCategory.getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<context:annotation-config />
|
||||
<context:component-scan base-package="org.alfresco" />
|
||||
|
||||
<import resource="classpath:dataprep-context.xml" />
|
||||
|
||||
<bean id="elasticsearchRESTHelper" class="org.alfresco.elasticsearch.utility.ElasticsearchRESTHelper" />
|
||||
<bean id="searchQueryService" class="org.alfresco.elasticsearch.SearchQueryService" />
|
||||
</beans>
|
||||
@@ -0,0 +1,5 @@
|
||||
# Exact Term search is switched off by default as it introduces index size overhead.
|
||||
#to enable it, please uncomment the following on a datatype or property name basis
|
||||
alfresco.cross.locale.datatype.0={http://www.alfresco.org/model/dictionary/1.0}content
|
||||
alfresco.cross.locale.property.0={http://www.alfresco.org/model/content/1.0}content
|
||||
alfresco.cross.locale.property.1={http://www.alfresco.org/model/content/1.0}name
|
||||
@@ -0,0 +1,16 @@
|
||||
# Root logger option
|
||||
rootLogger.level=info
|
||||
rootLogger.appenderRef.stdout.ref=ConsoleAppender
|
||||
|
||||
###### Console appender definition #######
|
||||
appender.console.type=Console
|
||||
appender.console.name=ConsoleAppender
|
||||
appender.console.layout.type=PatternLayout
|
||||
appender.console.layout.pattern=[%t] %d{HH:mm:ss} %-5p %c{1}:%L - %replace{%m}{[\r\n]+}{}%n
|
||||
|
||||
logger.alfresco-elasticsearch.name=org.alfresco.elasticsearch
|
||||
logger.alfresco-elasticsearch.level=info
|
||||
logger.alfresco-rest-core.name=org.alfresco.rest.core
|
||||
logger.alfresco-rest-core.level=warn
|
||||
logger.alfresco-utility-report-log.name=org.alfresco.utility.report.log
|
||||
logger.alfresco-utility-report-log.level=info
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
+12
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
|
||||
<suite name="REST API tests Elasticsearch with Basic Auth" preserve-order="true" verbose="1">
|
||||
<listeners>
|
||||
<listener class-name="org.alfresco.elasticsearch.retry.RetryAnnotationTransformer" />
|
||||
</listeners>
|
||||
<test name="elasticsearch-basic-auth" verbose="3" preserve-order="true">
|
||||
<packages>
|
||||
<package name="org.alfresco.elasticsearch.basicAuth"/>
|
||||
</packages>
|
||||
</test>
|
||||
</suite>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
|
||||
<suite name="REST API tests for Elasticsearch" verbose="1" parallel="false">
|
||||
<listeners>
|
||||
<listener class-name="org.alfresco.utility.testng.AlphabeticalPriorityInterceptor" />
|
||||
</listeners>
|
||||
<test name="elasticsearch" verbose="3" preserve-order="false">
|
||||
<packages>
|
||||
<package name="org.alfresco.elasticsearch" />
|
||||
</packages>
|
||||
</test>
|
||||
</suite>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
|
||||
<suite name="Parallel REST API tests for Elasticsearch" verbose="1" parallel="false">
|
||||
<listeners>
|
||||
<listener class-name="org.alfresco.elasticsearch.retry.RetryAnnotationTransformer" />
|
||||
</listeners>
|
||||
<suite-files>
|
||||
<suite-file path="elasticsearch-suite.xml" />
|
||||
</suite-files>
|
||||
</suite>
|
||||
@@ -0,0 +1,2 @@
|
||||
# By default this property is set to 30 seconds
|
||||
pull.pause.timeout = 120
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>content-repository-community-tests</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
|
||||
+31
-30
@@ -1,30 +1,31 @@
|
||||
package org.alfresco.email.imap;
|
||||
|
||||
import org.alfresco.email.EmailTest;
|
||||
import org.alfresco.utility.model.*;
|
||||
import org.alfresco.utility.testrail.ExecutionType;
|
||||
import org.alfresco.utility.testrail.annotation.TestRail;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class ImapReadMessagesAcsTests extends EmailTest
|
||||
{
|
||||
@BeforeClass(alwaysRun=true)
|
||||
public void dataPreparation() throws Exception
|
||||
{
|
||||
testUser = dataUser.createRandomTestUser();
|
||||
testSite = dataSite.usingUser(testUser).createIMAPSite();
|
||||
adminUser = dataUser.getAdminUser();
|
||||
adminSite = dataSite.usingAdmin().createIMAPSite();
|
||||
}
|
||||
|
||||
@TestRail(section = { TestGroup.PROTOCOLS, TestGroup.IMAP }, executionType = ExecutionType.REGRESSION,
|
||||
description = "Verify that site manager can see wiki pages via IMAP")
|
||||
@Test(groups = { TestGroup.PROTOCOLS, TestGroup.IMAP, TestGroup.CORE })
|
||||
public void siteManagerCanViewWikiPages() throws Exception
|
||||
{
|
||||
dataWiki.usingUser(testUser).usingSite(testSite).createRandomWiki();
|
||||
/* @Category(IntermittentlyFailingTests.class) ACS-959 Intermittent failure on next line. @Category not supported by TAS tests. */
|
||||
// ACS-2268 comment out: imapProtocol.authenticateUser(testUser).usingSiteWikiContainer(testSite).assertThat().countMessagesIs(1);
|
||||
}
|
||||
}
|
||||
package org.alfresco.email.imap;
|
||||
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.email.EmailTest;
|
||||
import org.alfresco.utility.model.*;
|
||||
import org.alfresco.utility.testrail.ExecutionType;
|
||||
import org.alfresco.utility.testrail.annotation.TestRail;
|
||||
|
||||
public class ImapReadMessagesAcsTests extends EmailTest
|
||||
{
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation() throws Exception
|
||||
{
|
||||
testUser = dataUser.createRandomTestUser();
|
||||
testSite = dataSite.usingUser(testUser).createIMAPSite();
|
||||
adminUser = dataUser.getAdminUser();
|
||||
adminSite = dataSite.usingAdmin().createIMAPSite();
|
||||
}
|
||||
|
||||
@TestRail(section = {TestGroup.PROTOCOLS, TestGroup.IMAP}, executionType = ExecutionType.REGRESSION,
|
||||
description = "Verify that site manager can see wiki pages via IMAP")
|
||||
@Test(groups = {TestGroup.PROTOCOLS, TestGroup.IMAP, TestGroup.CORE})
|
||||
public void siteManagerCanViewWikiPages() throws Exception
|
||||
{
|
||||
dataWiki.usingUser(testUser).usingSite(testSite).createRandomWiki();
|
||||
/* @Category(IntermittentlyFailingTests.class) ACS-959 Intermittent failure on next line. @Category not supported by TAS tests. */
|
||||
// ACS-2268 comment out: imapProtocol.authenticateUser(testUser).usingSiteWikiContainer(testSite).assertThat().countMessagesIs(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>content-repository-community-tests</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
<properties>
|
||||
<suiteXmlFile>${project.basedir}/src/test/resources/integration-suite.xml</suiteXmlFile>
|
||||
<dependency.groovy.version>3.0.19</dependency.groovy.version>
|
||||
<dependency.groovy.version>3.0.22</dependency.groovy.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -114,7 +114,7 @@
|
||||
<suiteXmlFiles>
|
||||
<suiteXmlFile>${suiteXmlFile}</suiteXmlFile>
|
||||
</suiteXmlFiles>
|
||||
<!-- Keeping illegal-access=warn for Java 11 compatibility, even though it has no effect on JDK 17 -->
|
||||
<!-- Keeping illegal-access=warn for Java 11 compatibility, even though it has no effect on JDK 21 -->
|
||||
<argLine>
|
||||
--illegal-access=warn
|
||||
--add-opens=java.base/java.lang=ALL-UNNAMED
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>content-repository-community-tests</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
@@ -90,7 +90,7 @@
|
||||
<suiteXmlFiles>
|
||||
<suiteXmlFile>${suiteXmlFile}</suiteXmlFile>
|
||||
</suiteXmlFiles>
|
||||
<!-- Keeping illegal-access=warn for Java 11 compatibility, even though it has no effect on JDK 17 -->
|
||||
<!-- Keeping illegal-access=warn for Java 11 compatibility, even though it has no effect on JDK 21 -->
|
||||
<argLine>
|
||||
--illegal-access=warn
|
||||
--add-opens=java.base/java.lang=ALL-UNNAMED
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>content-repository-community-tests</artifactId>
|
||||
<version>23.2.0-A12</version>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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>
|
||||
<groupId>org.alfresco.tas</groupId>
|
||||
<artifactId>content-repository-community-testcontainers</artifactId>
|
||||
<name>Testcontainers environment</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>content-repository-community-tests</artifactId>
|
||||
<version>26.2.0-A.31-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<dependency.testcontainers.version>2.0.3</dependency.testcontainers.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.alfresco.tas</groupId>
|
||||
<artifactId>restapi</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers-postgresql</artifactId>
|
||||
<version>${dependency.testcontainers.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers-mysql</artifactId>
|
||||
<version>${dependency.testcontainers.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers-mariadb</artifactId>
|
||||
<version>${dependency.testcontainers.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers-elasticsearch</artifactId>
|
||||
<version>${dependency.testcontainers.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers-mssqlserver</artifactId>
|
||||
<version>${dependency.testcontainers.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
</project>
|
||||
+521
@@ -0,0 +1,521 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Testcontainers Environment
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.tas;
|
||||
|
||||
import static org.alfresco.tas.SystemPropertyHelper.getSystemProperty;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.test.context.support.TestPropertySourceUtils;
|
||||
import org.testcontainers.containers.BindMode;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.JdbcDatabaseContainer;
|
||||
import org.testcontainers.containers.MariaDBContainer;
|
||||
import org.testcontainers.containers.MySQLContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
import org.testcontainers.containers.output.Slf4jLogConsumer;
|
||||
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.lifecycle.Startable;
|
||||
import org.testcontainers.lifecycle.Startables;
|
||||
import org.testng.Assert;
|
||||
import org.testng.util.Strings;
|
||||
|
||||
import org.alfresco.utility.report.log.Step;
|
||||
|
||||
public class AlfrescoStackInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AlfrescoStackInitializer.class);
|
||||
private static final Slf4jLogConsumer LOG_CONSUMER = new Slf4jLogConsumer(LOGGER);
|
||||
|
||||
public static final String CUSTOM_ALFRESCO_INDEX = "custom-alfresco-index";
|
||||
|
||||
public static Network network;
|
||||
|
||||
public static GenericContainer alfresco;
|
||||
|
||||
public static GenericContainer searchEngineContainer;
|
||||
|
||||
/** To create the kibana container for a test run then pass -Dkibana=true as an argument to the mvn command. */
|
||||
public static GenericContainer dashboardsContainer;
|
||||
|
||||
/** Community Elasticsearch based indexer. */
|
||||
public static GenericContainer batchIndexer;
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext configurableApplicationContext)
|
||||
{
|
||||
|
||||
// Wait till existing containers are stopped
|
||||
if (alfresco != null)
|
||||
{
|
||||
if (alfresco.getDockerClient().listContainersCmd().withShowAll(true).exec().size() > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
LOGGER.info("Waiting for living containers to be stopped...");
|
||||
Thread.sleep(10000);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
network = Network.newNetwork();
|
||||
|
||||
alfresco = createAlfrescoContainer();
|
||||
|
||||
JdbcDatabaseContainer database = createDatabaseContainer();
|
||||
|
||||
GenericContainer transformCore = createTransformCoreContainer();
|
||||
|
||||
GenericContainer activemq = createAMQContainer();
|
||||
|
||||
searchEngineContainer = createSearchEngineContainer();
|
||||
|
||||
startOrFail(searchEngineContainer);
|
||||
|
||||
configureSecuritySettings(searchEngineContainer);
|
||||
|
||||
startOrFail(database);
|
||||
|
||||
startOrFail(activemq);
|
||||
|
||||
startOrFail(transformCore);
|
||||
|
||||
// We don't want Kibana to run on our CI, but it can be useful when investigating issues locally.
|
||||
if (getSystemProperty("kibana", "false").equals("true"))
|
||||
{
|
||||
dashboardsContainer = createDashboardsContainer();
|
||||
startOrFail(dashboardsContainer);
|
||||
}
|
||||
|
||||
startOrFail(alfresco);
|
||||
|
||||
alfresco.followOutput(LOG_CONSUMER);
|
||||
|
||||
batchIndexer = createBatchIndexingContainer();
|
||||
|
||||
startOrFail(batchIndexer);
|
||||
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(configurableApplicationContext,
|
||||
"alfresco.server=" + alfresco.getContainerIpAddress(),
|
||||
"alfresco.port=" + alfresco.getFirstMappedPort());
|
||||
|
||||
}
|
||||
|
||||
private JdbcDatabaseContainer createDatabaseContainer()
|
||||
{
|
||||
switch (getImagesConfig().getDatabaseType())
|
||||
{
|
||||
case POSTGRESQL_DB:
|
||||
return createPosgresContainer();
|
||||
case MYSQL_DB:
|
||||
return createMySqlContainer();
|
||||
case MARIA_DB:
|
||||
return createMariaDBContainer();
|
||||
default:
|
||||
throw new IllegalArgumentException("Database not set.");
|
||||
}
|
||||
}
|
||||
|
||||
public void configureSecuritySettings(GenericContainer searchEngineContainer)
|
||||
{
|
||||
// empty for default execution
|
||||
}
|
||||
|
||||
private void startOrFail(Startable... startables)
|
||||
{
|
||||
try
|
||||
{
|
||||
Startables.deepStart(startables).get();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.fail("Unable to start containers", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected GenericContainer<?> createBatchIndexingContainer()
|
||||
{
|
||||
DatabaseType databaseType = getImagesConfig().getDatabaseType();
|
||||
String driverJarPath = resolveDriverJarPath(databaseType.getDriver());
|
||||
String driverJarName = Paths.get(driverJarPath).getFileName().toString();
|
||||
|
||||
return new GenericContainer<>(getImagesConfig().getBatchIndexingImage())
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("batch-indexer")
|
||||
.withEnv("JAVA_OPTS", "-Xms512m -Xmx1536m -agentlib:jdwp=transport=dt_socket,address=*:5005,server=y,suspend=n")
|
||||
.withEnv("SPRING_DATASOURCE_URL", databaseType.getUrl())
|
||||
.withEnv("SPRING_DATASOURCE_USERNAME", databaseType.getUsername())
|
||||
.withEnv("SPRING_DATASOURCE_PASSWORD", databaseType.getPassword())
|
||||
.withEnv("SPRING_ELASTICSEARCH_REST_URIS", "http://elasticsearch:9200")
|
||||
.withEnv("ELASTICSEARCH_INDEXNAME", CUSTOM_ALFRESCO_INDEX)
|
||||
.withEnv("ALFRESCO_ACCEPTEDCONTENTMEDIATYPESCACHE_ENABLED", "false")
|
||||
.withEnv("ALFRESCO_ACS_URL", "http://alfresco:8080")
|
||||
.withEnv("ALFRESCO_CONTENT_TRANSFORM_SHAREDSECRET", "secret")
|
||||
.withEnv("ALFRESCO_REINDEX_BATCHSIZE", "1000")
|
||||
.withEnv("ALFRESCO_REINDEX_PAGESIZE", "1000")
|
||||
.withEnv("ALFRESCO_REINDEX_CONTINUOUS_POLLINGINTERVAL", "1s")
|
||||
.withEnv("ALFRESCO_REINDEX_CONTINUOUS_CATCHUPPOLLINGINTERVAL", "100ms")
|
||||
.withFileSystemBind(driverJarPath, "/opt/db-drivers/" + driverJarName, BindMode.READ_ONLY)
|
||||
.withExposedPorts(5005);
|
||||
}
|
||||
|
||||
private static String resolveDriverJarPath(String driverClassName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> driverClass = Class.forName(driverClassName);
|
||||
URI jarUri = driverClass.getProtectionDomain().getCodeSource().getLocation().toURI();
|
||||
return Paths.get(jarUri).toString();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IllegalStateException("Could not resolve JAR for JDBC driver class: " + driverClassName, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected GenericContainer createSearchEngineContainer()
|
||||
{
|
||||
return getImagesConfig().getSearchEngineType() == SearchEngineType.OPENSEARCH_ENGINE ? createOpensearchContainer() : createElasticContainer();
|
||||
}
|
||||
|
||||
protected GenericContainer createDashboardsContainer()
|
||||
{
|
||||
return getImagesConfig().getSearchEngineType() == SearchEngineType.OPENSEARCH_ENGINE ? createOpensearchDashboardsContainer() : createKibanaContainer();
|
||||
}
|
||||
|
||||
protected GenericContainer createElasticContainer()
|
||||
{
|
||||
return new GenericContainer<>(getImagesConfig().getElasticsearchImage())
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("elasticsearch")
|
||||
.withExposedPorts(9200)
|
||||
.withEnv("xpack.security.enabled", "false")
|
||||
.withEnv("discovery.type", "single-node")
|
||||
.withEnv("ES_JAVA_OPTS", "-Xms2g -Xmx2g");
|
||||
}
|
||||
|
||||
protected GenericContainer createOpensearchContainer()
|
||||
{
|
||||
return new GenericContainer<>(getImagesConfig().getOpensearchImage())
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("elasticsearch")
|
||||
.withExposedPorts(9200)
|
||||
.withEnv("plugins.security.disabled", "true")
|
||||
.withEnv("discovery.type", "single-node")
|
||||
.withEnv("OPENSEARCH_JAVA_OPTS", "-Xms2g -Xmx2g");
|
||||
}
|
||||
|
||||
protected GenericContainer createOpensearchDashboardsContainer()
|
||||
{
|
||||
return new GenericContainer(getImagesConfig().getOpensearchDashboardsImage())
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("kibana")
|
||||
.withExposedPorts(5601)
|
||||
.withEnv("ELASTICSEARCH_HOSTS", "http://elasticsearch:9200");
|
||||
}
|
||||
|
||||
protected GenericContainer createKibanaContainer()
|
||||
{
|
||||
return new GenericContainer(getImagesConfig().getKibanaImage())
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("kibana")
|
||||
.withExposedPorts(5601)
|
||||
.withEnv("ELASTICSEARCH_HOSTS", "http://elasticsearch:9200");
|
||||
}
|
||||
|
||||
private GenericContainer createAMQContainer()
|
||||
{
|
||||
return new GenericContainer(getImagesConfig().getActiveMqImage())
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("activemq")
|
||||
.withEnv("JAVA_OPTS", "-Xms512m -Xmx1g")
|
||||
.waitingFor(Wait.forListeningPort())
|
||||
.withStartupTimeout(Duration.ofMinutes(2))
|
||||
.withExposedPorts(61616, 8161, 5672, 61613);
|
||||
}
|
||||
|
||||
private PostgreSQLContainer createPosgresContainer()
|
||||
{
|
||||
return (PostgreSQLContainer) new PostgreSQLContainer(getImagesConfig().getPostgreSQLImage())
|
||||
.withPassword(DatabaseType.POSTGRESQL_DB.getPassword())
|
||||
.withUsername(DatabaseType.POSTGRESQL_DB.getUsername())
|
||||
.withDatabaseName("alfresco")
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("postgres")
|
||||
.withStartupTimeout(Duration.ofMinutes(2));
|
||||
}
|
||||
|
||||
private MySQLContainer createMySqlContainer()
|
||||
{
|
||||
return (MySQLContainer) new MySQLContainer(getImagesConfig().getMySQLImage())
|
||||
.withPassword(DatabaseType.MYSQL_DB.getPassword())
|
||||
.withUsername(DatabaseType.MYSQL_DB.getUsername())
|
||||
.withDatabaseName("alfresco")
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("mysql")
|
||||
.withStartupTimeout(Duration.ofMinutes(2));
|
||||
|
||||
}
|
||||
|
||||
private MariaDBContainer createMariaDBContainer()
|
||||
{
|
||||
return new MariaDBContainer<>(getImagesConfig().getMariaDBImage())
|
||||
.withPassword(DatabaseType.MARIA_DB.getPassword())
|
||||
.withUsername(DatabaseType.MARIA_DB.getUsername())
|
||||
.withDatabaseName("alfresco")
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("mariadb")
|
||||
.withStartupTimeout(Duration.ofMinutes(2));
|
||||
|
||||
}
|
||||
|
||||
private GenericContainer createTransformCoreContainer()
|
||||
{
|
||||
return new GenericContainer(getImagesConfig().getTransformCoreAIOImage())
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("transform-core-aio")
|
||||
.withEnv("JAVA_OPTS", "-Xms512m -Xmx1024m")
|
||||
.withEnv("ACTIVEMQ_URL", "nio://activemq:61616")
|
||||
.withEnv("ACTIVEMQ_USER", "admin")
|
||||
.withEnv("ACTIVEMQ_PASSWORD", "admin")
|
||||
.withEnv("FILE_STORE_URL", "http://shared-file-store:8099/alfresco/api/-default-/private/sfs/versions/1/file")
|
||||
.withExposedPorts(8090)
|
||||
.waitingFor(Wait.forListeningPort())
|
||||
.withStartupTimeout(Duration.ofMinutes(2));
|
||||
}
|
||||
|
||||
protected GenericContainer createAlfrescoContainer()
|
||||
{
|
||||
DatabaseType databaseType = getImagesConfig().getDatabaseType();
|
||||
return new GenericContainer(getImagesConfig().getRepositoryImage())
|
||||
.withEnv("CATALINA_OPTS", "\"-agentlib:jdwp=transport=dt_socket,address=*:8000,server=y,suspend=n\"")
|
||||
.withEnv("JAVA_TOOL_OPTIONS",
|
||||
"-Dencryption.keystore.type=JCEKS " +
|
||||
"-Dencryption.cipherAlgorithm=DESede/CBC/PKCS5Padding " +
|
||||
"-Dencryption.keyAlgorithm=DESede " +
|
||||
"-Dencryption.keystore.location=/usr/local/tomcat/shared/classes/alfresco/extension/keystore/keystore " +
|
||||
"-Dmetadata-keystore.password=mp6yc0UD9e -Dmetadata-keystore.aliases=metadata " +
|
||||
"-Dmetadata-keystore.metadata.password=oKIWzVdEdA -Dmetadata-keystore.metadata.algorithm=DESede")
|
||||
.withEnv("JAVA_OPTS",
|
||||
"-Delasticsearch.createIndexIfNotExists=true " +
|
||||
"-Dindex.subsystem.name=elasticsearch " +
|
||||
"-Delasticsearch.host=elasticsearch " +
|
||||
"-Dsolr.secureComms=secret " +
|
||||
"-Dsolr.sharedSecret=secret " +
|
||||
"-Delasticsearch.indexName=" + CUSTOM_ALFRESCO_INDEX + " " +
|
||||
"-Ddb.driver=" + databaseType.getDriver() + " " +
|
||||
"-Ddb.url=" + escapeSemicolonInUrlForJavaOptsUsage(databaseType.getUrl()) + " " +
|
||||
"-Ddb.username=" + databaseType.getUsername() + " " +
|
||||
"-Ddb.password=" + databaseType.getPassword() + " " +
|
||||
indentDbSettings(databaseType.getAdditionalDbSettings()) +
|
||||
"-Dshare.host=127.0.0.1 " +
|
||||
"-Dshare.port=8080 " +
|
||||
"-Dalfresco.host=localhost " +
|
||||
"-Dalfresco.port=8080 " +
|
||||
"-Daos.baseUrlOverwrite=http://localhost:8080/alfresco/aos " +
|
||||
"-Dmessaging.broker.url=\"failover:(nio://activemq:61616)?timeout=3000&jms.useCompression=true\" " +
|
||||
"-Dmessaging.broker.username=admin " +
|
||||
"-Dmessaging.broker.password=admin " +
|
||||
"-Ddeployment.method=DOCKER_COMPOSE " +
|
||||
"-Dtransform.service.enabled=true " +
|
||||
"-Dtransform.service.url=http://transform-router:8095 " +
|
||||
"-Dsfs.url=http://shared-file-store:8099 " +
|
||||
"-DlocalTransform.core-aio.url=http://transform-core-aio:8090/ " +
|
||||
"-Dcsrf.filter.enabled=false " +
|
||||
"-Dalfresco.restApi.basicAuthScheme=true " +
|
||||
"-Dquery.cmis.queryConsistency=EVENTUAL " +
|
||||
"-Dquery.fts.queryConsistency=EVENTUAL " +
|
||||
"-Xms1g -Xmx2g ")
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("alfresco")
|
||||
.waitingFor(new LogMessageWaitStrategy().withRegEx(".*Server startup in.*\\n"))
|
||||
.withStartupTimeout(Duration.ofMinutes(7))
|
||||
.withExposedPorts(8080, 8000)
|
||||
.withClasspathResourceMapping("exactTermSearch.properties",
|
||||
"/usr/local/tomcat/webapps/alfresco/WEB-INF/classes/alfresco/search/elasticsearch/config/exactTermSearch.properties",
|
||||
BindMode.READ_ONLY);
|
||||
}
|
||||
|
||||
private String escapeSemicolonInUrlForJavaOptsUsage(String url)
|
||||
{
|
||||
return url.replace(";", "\\;");
|
||||
}
|
||||
|
||||
private String indentDbSettings(Map<String, String> additionalDbSettings)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Map.Entry<String, String> setting : additionalDbSettings.entrySet())
|
||||
{
|
||||
sb.append("-Ddb.").append(setting.getKey()).append("=").append(setting.getValue()).append(" ");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static ImagesConfig getImagesConfig()
|
||||
{
|
||||
return DefaultImagesConfig.INSTANCE;
|
||||
}
|
||||
|
||||
public interface ImagesConfig
|
||||
{
|
||||
String getBatchIndexingImage();
|
||||
|
||||
String getElasticsearchImage();
|
||||
|
||||
String getOpensearchImage();
|
||||
|
||||
String getOpensearchDashboardsImage();
|
||||
|
||||
String getActiveMqImage();
|
||||
|
||||
String getTransformCoreAIOImage();
|
||||
|
||||
String getPostgreSQLImage();
|
||||
|
||||
String getMySQLImage();
|
||||
|
||||
String getMariaDBImage();
|
||||
|
||||
DatabaseType getDatabaseType();
|
||||
|
||||
String getRepositoryImage();
|
||||
|
||||
String getKibanaImage();
|
||||
|
||||
SearchEngineType getSearchEngineType();
|
||||
}
|
||||
|
||||
private record DefaultImagesConfig(Function<String, String> envProperties, Function<String, String> mavenProperties) implements ImagesConfig
|
||||
{
|
||||
private static final DefaultImagesConfig INSTANCE = new DefaultImagesConfig(EnvHelper::getEnvProperty, MavenPropertyHelper::getMavenProperty);
|
||||
|
||||
@Override
|
||||
public String getBatchIndexingImage()
|
||||
{
|
||||
return getSystemProperty("indeximage", "alfresco/alfresco-elasticsearch-batch-indexing:local");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElasticsearchImage()
|
||||
{
|
||||
return "docker.elastic.co/elasticsearch/elasticsearch:" + envProperties.apply("ELASTICSEARCH_TAG");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOpensearchImage()
|
||||
{
|
||||
return "opensearchproject/opensearch:" + envProperties.apply("OPENSEARCH_TAG");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOpensearchDashboardsImage()
|
||||
{
|
||||
return "opensearchproject/opensearch-dashboards:" + envProperties.apply("OPENSEARCH_DASHBOARDS_TAG");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActiveMqImage()
|
||||
{
|
||||
return "alfresco/alfresco-activemq:" + envProperties.apply("ACTIVEMQ_TAG");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTransformCoreAIOImage()
|
||||
{
|
||||
return "alfresco/alfresco-transform-core-aio:" + mavenProperties.apply("dependency.alfresco-transform-core.version");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPostgreSQLImage()
|
||||
{
|
||||
return "postgres:" + envProperties.apply("POSTGRES_TAG");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMySQLImage()
|
||||
{
|
||||
return "mysql:" + envProperties.apply("MYSQL_TAG");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMariaDBImage()
|
||||
{
|
||||
return "mariadb:" + envProperties.apply("MARIADB_TAG");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRepositoryImage()
|
||||
{
|
||||
return getSystemProperty("repoimage", "alfresco/alfresco-content-repository-community:latest");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKibanaImage()
|
||||
{
|
||||
return "kibana:" + envProperties.apply("KIBANA_TAG");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchEngineType getSearchEngineType()
|
||||
{
|
||||
String searchEngineTypeProperty = mavenProperties.apply("search.engine.type");
|
||||
if (Strings.isNullOrEmpty(searchEngineTypeProperty))
|
||||
{
|
||||
Step.STEP("Defaulting search engine to Elasticsearch.");
|
||||
return SearchEngineType.ELASTICSEARCH_ENGINE;
|
||||
}
|
||||
return SearchEngineType.from(searchEngineTypeProperty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseType getDatabaseType()
|
||||
{
|
||||
String databaseTypeProperty = mavenProperties.apply("database.type");
|
||||
if (Strings.isNullOrEmpty(databaseTypeProperty))
|
||||
{
|
||||
Step.STEP("Defaulting database to postgresql.");
|
||||
return DatabaseType.POSTGRESQL_DB;
|
||||
}
|
||||
return DatabaseType.from(databaseTypeProperty);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Testcontainers Environment
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.tas;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum DatabaseType
|
||||
{
|
||||
POSTGRESQL_DB("postgresql", "org.postgresql.Driver", "jdbc:postgresql://postgres:5432/alfresco", "alfresco", "alfresco"), MYSQL_DB("mysql", "com.mysql.cj.jdbc.Driver", "jdbc:mysql://mysql:3306/alfresco", "alfresco", "alfresco"), MARIA_DB("mariadb", "org.mariadb.jdbc.Driver", "jdbc:mariadb://mariadb:3306/alfresco", "alfresco", "alfresco");
|
||||
|
||||
private final String type;
|
||||
private final String driver;
|
||||
private final String url;
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final Map<String, String> additionalDbSettings;
|
||||
|
||||
DatabaseType(String type, String driver, String url, String username, String password, Map<String, String> additionalDbSettings)
|
||||
{
|
||||
this.type = type;
|
||||
this.driver = driver;
|
||||
this.url = url;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.additionalDbSettings = additionalDbSettings;
|
||||
}
|
||||
|
||||
DatabaseType(String type, String driver, String url, String username, String password)
|
||||
{
|
||||
this.type = type;
|
||||
this.driver = driver;
|
||||
this.url = url;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.additionalDbSettings = new HashMap<>(0);
|
||||
}
|
||||
|
||||
public String getType()
|
||||
{
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public String getDriver()
|
||||
{
|
||||
return driver;
|
||||
}
|
||||
|
||||
public String getUrl()
|
||||
{
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getUsername()
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getPassword()
|
||||
{
|
||||
return password;
|
||||
}
|
||||
|
||||
public Map<String, String> getAdditionalDbSettings()
|
||||
{
|
||||
return additionalDbSettings;
|
||||
}
|
||||
|
||||
public static DatabaseType from(String type)
|
||||
{
|
||||
return Arrays.stream(DatabaseType.values())
|
||||
.filter(database -> database.getType().equals(type.toLowerCase()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Database of type + '" + type + "' not defined."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Testcontainers Environment
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.tas;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.testng.Assert;
|
||||
|
||||
/** A helper class with methods to access properties from the .env file. */
|
||||
public class EnvHelper
|
||||
{
|
||||
/** The location of the .env file (relative to the root of this maven submodule). */
|
||||
private static final String ENV_FILE_NAME = "../environment/.env";
|
||||
|
||||
/** Private constructor for helper class. */
|
||||
private EnvHelper()
|
||||
{}
|
||||
|
||||
/**
|
||||
* Load the value of a property from the .env file.
|
||||
*
|
||||
* @param key
|
||||
* The key to look up.
|
||||
* @return The value as a string.
|
||||
*/
|
||||
public static String getEnvProperty(String key)
|
||||
{
|
||||
return loadEnvProperties().getProperty(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all the properties from the .env file.
|
||||
*
|
||||
* @return The properties.
|
||||
*/
|
||||
public static Properties loadEnvProperties()
|
||||
{
|
||||
Properties env = new Properties();
|
||||
try (FileReader reader = new FileReader(ENV_FILE_NAME))
|
||||
{
|
||||
env.load(reader);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.fail("unable to load .env property file ");
|
||||
}
|
||||
return env;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Testcontainers Environment
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.tas;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.testng.Assert;
|
||||
|
||||
/** A helper class with methods to access properties from Maven pom files. */
|
||||
public class MavenPropertyHelper
|
||||
{
|
||||
/** The relative path to the maven property file resource. */
|
||||
private static final String MAVEN_PROPERTIES_FILE = "maven.properties";
|
||||
|
||||
/** Private constructor for helper class. */
|
||||
private MavenPropertyHelper()
|
||||
{}
|
||||
|
||||
/**
|
||||
* Load the value of a property from Maven.
|
||||
*
|
||||
* @param key
|
||||
* The key to look up.
|
||||
* @return The value as a string.
|
||||
*/
|
||||
public static String getMavenProperty(String key)
|
||||
{
|
||||
String value = loadMavenProperties().getProperty(key);
|
||||
// If Maven filtering hasn't run, the value will be an
|
||||
// unresolved placeholder like "${database.type}". Fall back to the JVM system property instead.
|
||||
if (value != null && value.startsWith("${") && value.endsWith("}"))
|
||||
{
|
||||
value = System.getProperty(key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all the properties from Maven.
|
||||
*
|
||||
* @return The properties.
|
||||
*/
|
||||
public static Properties loadMavenProperties()
|
||||
{
|
||||
Properties properties = new Properties();
|
||||
try (InputStream inputStream = MavenPropertyHelper.class.getClassLoader().getResourceAsStream(MAVEN_PROPERTIES_FILE))
|
||||
{
|
||||
Reader reader = new InputStreamReader(inputStream);
|
||||
properties.load(reader);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.fail("Unable to load maven.properties file ");
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Testcontainers Environment
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.tas;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public enum SearchEngineType
|
||||
{
|
||||
OPENSEARCH_ENGINE("opensearch"), ELASTICSEARCH_ENGINE("elasticsearch");
|
||||
|
||||
private final String type;
|
||||
|
||||
SearchEngineType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getType()
|
||||
{
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public static SearchEngineType from(String type)
|
||||
{
|
||||
return Arrays.stream(SearchEngineType.values())
|
||||
.filter(engine -> engine.getType().equals(type.toLowerCase()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Search engine of type '" + type + "' not defined."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Testcontainers Environment
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.tas;
|
||||
|
||||
/** A helper class with methods to access system properties, e.g. those passed via -D maven command line arguments. */
|
||||
public class SystemPropertyHelper
|
||||
{
|
||||
/** Private constructor for helper class. */
|
||||
private SystemPropertyHelper()
|
||||
{}
|
||||
|
||||
/**
|
||||
* Load the value of a property.
|
||||
*
|
||||
* @param key
|
||||
* The key to look up.
|
||||
* @param defaultValue
|
||||
* The default value to use if the property is not set.
|
||||
* @return The value as a string.
|
||||
*/
|
||||
public static String getSystemProperty(String key, String defaultValue)
|
||||
{
|
||||
String value = System.getProperty(key);
|
||||
if (value == null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Testcontainers Environment
|
||||
* %%
|
||||
* Copyright (C) 2026 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.tas;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/** A class providing utility methods for e2e testing. */
|
||||
public class TestDataUtility
|
||||
{
|
||||
private static final Random RANDOM = new Random();
|
||||
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
/** Generate a random 'word' containing 16 alphabetic characters. */
|
||||
public static String getAlphabeticUUID()
|
||||
{
|
||||
return IntStream.range(0, 16)
|
||||
.map(i -> ALPHABET.charAt(RANDOM.nextInt(ALPHABET.length())))
|
||||
.mapToObj(Character::toString)
|
||||
.collect(Collectors.joining());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
database.type=${database.type}
|
||||
search.engine.type=${search.engine.type}
|
||||
dependency.alfresco-transform-service.version=${dependency.alfresco-transform-service.version}
|
||||
dependency.alfresco-transform-core.version=${dependency.alfresco-transform-core.version}
|
||||
|
||||
Reference in New Issue
Block a user