Compare commits

...
491 Commits
Author SHA1 Message Date
alfresco-build 81df71adcb [maven-release-plugin][skip ci] prepare release 26.2.0-A.29 2026-07-16 09:22:06 +00:00
alfresco-build 75d57e4806 [release] 26.2.0-A.29
Update upstream versions
    - alfresco-community-repo:   26.2.0.92
    - alfresco-enterprise-share: 26.2.0.65
    - acs-packaging:             26.2.0-A.29
    - RELEASE_VERSION:           26.2.0-A.29
    - DEVELOPMENT_VERSION:       26.2.0-A.30-SNAPSHOT
2026-07-16 07:02:13 +00:00
alfresco-build 92b39d478f [maven-release-plugin][skip ci] prepare for next development iteration 2026-07-13 19:29:59 +00:00
alfresco-build 5c84333cc2 [maven-release-plugin][skip ci] prepare release 26.2.0-A.28 2026-07-13 19:29:56 +00:00
alfresco-build 29338fd53e [release] 26.2.0-A.28
Update upstream versions
    - alfresco-community-repo:   26.2.0.91
    - alfresco-enterprise-share: 26.2.0.64
    - acs-packaging:             26.2.0-A.28
    - RELEASE_VERSION:           26.2.0-A.28
    - DEVELOPMENT_VERSION:       26.2.0-A.29-SNAPSHOT
2026-07-13 16:55:32 +00:00
Satyam Sah 0e5c7c9465 ACS-12161 Bump Batch Indexing to 5.7.0-A.5 (#1686)
* ACS-12161 Bump Batch Indexing to 5.7.0-A.5

* ACS-12161 Added newly introduced env parameter
2026-07-13 15:06:46 +05:30
alfresco-build 52832f1100 [maven-release-plugin][skip ci] prepare for next development iteration 2026-07-13 05:58:07 +00:00
alfresco-build b7afbb57d6 [maven-release-plugin][skip ci] prepare release 26.2.0-A.27 2026-07-13 05:58:04 +00:00
alfresco-build 4e1f805bff [release] 26.2.0-A.27
Update upstream versions
    - alfresco-community-repo:   26.2.0.90
    - alfresco-enterprise-share: 26.2.0.63
    - acs-packaging:             26.2.0-A.27
    - RELEASE_VERSION:           26.2.0-A.27
    - DEVELOPMENT_VERSION:       26.2.0-A.28-SNAPSHOT
2026-07-12 01:51:12 +00:00
Grzegorz Oleksy 6183411b72 [ACS-12160] Bump IE to 2.0.21-A.2 (#1684) 2026-07-10 14:06:22 +02:00
alfresco-build 691fee053f [maven-release-plugin][skip ci] prepare for next development iteration 2026-07-07 13:43:27 +00:00
alfresco-build 2658664376 [maven-release-plugin][skip ci] prepare release 26.2.0-A.26 2026-07-07 13:43:24 +00:00
alfresco-build a4d1b7c37f [release] 26.2.0-A.26
Update upstream versions
    - alfresco-community-repo:   26.2.0.83
    - alfresco-enterprise-share: 26.2.0.56
    - acs-packaging:             26.2.0-A.26
    - RELEASE_VERSION:           26.2.0-A.26
    - DEVELOPMENT_VERSION:       26.2.0-A.27-SNAPSHOT
2026-07-07 12:32:05 +00:00
alfresco-build 5f1ba33c6b [maven-release-plugin][skip ci] prepare for next development iteration 2026-07-06 12:46:42 +00:00
alfresco-build 7c0662abe7 [maven-release-plugin][skip ci] prepare release 26.2.0-A.25 2026-07-06 12:46:39 +00:00
alfresco-build 0ade3eb669 [release] 26.2.0-A.25
Update upstream versions
    - alfresco-community-repo:   26.2.0.83
    - alfresco-enterprise-share: 26.2.0.53
    - acs-packaging:             26.2.0-A.25
    - RELEASE_VERSION:           26.2.0-A.25
    - DEVELOPMENT_VERSION:       26.2.0-A.26-SNAPSHOT
2026-07-06 11:37:35 +00:00
alfresco-build f1bae17140 [maven-release-plugin][skip ci] prepare for next development iteration 2026-07-06 09:35:30 +00:00
alfresco-build b64c65f000 [maven-release-plugin][skip ci] prepare release 26.2.0-A.24 2026-07-06 09:35:26 +00:00
alfresco-build 035fd9ca7c [release] 26.2.0-A.24
Update upstream versions
    - alfresco-community-repo:   26.2.0.83
    - alfresco-enterprise-share: 26.2.0.53
    - acs-packaging:             26.2.0-A.24
    - RELEASE_VERSION:           26.2.0-A.24
    - DEVELOPMENT_VERSION:       26.2.0-A.25-SNAPSHOT
2026-07-05 01:52:23 +00:00
Satyam Sah 3dc624a652 ACS-11435 Elasticsearch TAS Tests (#1682)
* ACS-11434 testing in community packaging

* ACS-11434 minor fixes

* ACS-11434 added tag indexing tests

* ACS-11434 refactored tests

* ACS-11434 precommit fix

* ACS-11434 addressed copilot comments

* ACS-11434 addressed pr comments

* ACS-11434 Update batch indexing image to latest alpha

* ACS-11434 activate all-tas-tests profile while build

* ACS-11434 fix ci build

* ACS-11434 fix ci build

* ACS-11434 fix ci build

* ACS-11434 Sync with master

* ACS-11434 fix ci

* ACS-11434 add missing env parameters

* ACS-11434 ci fix

* ACS-11434 trim whitespace from batch indexing tag

* ACS-11434 Added alfresco with jdbc jars config

* ACS-11434 Fix basic auth tests and remove testrail

* ACS-11434 Fix precommit

* ACS-11434 Added retry listeners for basic auth test suite

* ACS-11434 Fix opensearch basicauth tests
2026-07-02 11:34:03 +05:30
alfresco-build 60bf0b5b14 [maven-release-plugin][skip ci] prepare for next development iteration 2026-06-28 02:57:51 +00:00
alfresco-build 6fbab4c900 [maven-release-plugin][skip ci] prepare release 26.2.0-A.23 2026-06-28 02:57:49 +00:00
alfresco-build d6dd22a2a0 [release] 26.2.0-A.23
Update upstream versions
    - alfresco-community-repo:   26.2.0.80
    - alfresco-enterprise-share: 26.2.0.50
    - acs-packaging:             26.2.0-A.23
    - RELEASE_VERSION:           26.2.0-A.23
    - DEVELOPMENT_VERSION:       26.2.0-A.24-SNAPSHOT
2026-06-28 01:49:59 +00:00
alfresco-build f9d8167d35 [maven-release-plugin][skip ci] prepare for next development iteration 2026-06-23 20:19:04 +00:00
alfresco-build bf2e0cc91a [maven-release-plugin][skip ci] prepare release 26.2.0-A.22 2026-06-23 20:19:00 +00:00
alfresco-build 5d1ee8c4c0 [release] 26.2.0-A.22
Update upstream versions
    - alfresco-community-repo:   26.2.0.77
    - alfresco-enterprise-share: 26.2.0.47
    - acs-packaging:             26.2.0-A.22
    - RELEASE_VERSION:           26.2.0-A.22
    - DEVELOPMENT_VERSION:       26.2.0-A.23-SNAPSHOT
2026-06-23 19:07:24 +00:00
alfresco-build 60a470da91 [maven-release-plugin][skip ci] prepare for next development iteration 2026-06-22 10:17:31 +00:00
alfresco-build 9ef8d49f80 [maven-release-plugin][skip ci] prepare release 26.2.0-A.21 2026-06-22 10:17:28 +00:00
alfresco-build 2ed27931a5 [release] 26.2.0-A.21
Update upstream versions
    - alfresco-community-repo:   26.2.0.73
    - alfresco-enterprise-share: 26.2.0.44
    - acs-packaging:             26.2.0-A.21
    - RELEASE_VERSION:           26.2.0-A.21
    - DEVELOPMENT_VERSION:       26.2.0-A.22-SNAPSHOT
2026-06-21 01:57:25 +00:00
alfresco-build 5af1ad824e [maven-release-plugin][skip ci] prepare for next development iteration 2026-06-15 08:57:31 +00:00
alfresco-build 975646f422 [maven-release-plugin][skip ci] prepare release 26.2.0-A.20 2026-06-15 08:57:28 +00:00
alfresco-build 32d42e8d5f [release] 26.2.0-A.20
Update upstream versions
    - alfresco-community-repo:   26.2.0.65
    - alfresco-enterprise-share: 26.2.0.41
    - acs-packaging:             26.2.0-A.20
    - RELEASE_VERSION:           26.2.0-A.20
    - DEVELOPMENT_VERSION:       26.2.0-A.21-SNAPSHOT
2026-06-15 07:43:44 +00:00
alfresco-build c644f08501 [maven-release-plugin][skip ci] prepare for next development iteration 2026-06-09 14:49:36 +00:00
alfresco-build fd4c548e4b [maven-release-plugin][skip ci] prepare release 26.2.0-A.19 2026-06-09 14:49:33 +00:00
alfresco-build f01f4636ba [release] 26.2.0-A.19
Update upstream versions
    - alfresco-community-repo:   26.2.0.62
    - alfresco-enterprise-share: 26.2.0.38
    - acs-packaging:             26.2.0-A.19
    - RELEASE_VERSION:           26.2.0-A.19
    - DEVELOPMENT_VERSION:       26.2.0-A.20-SNAPSHOT
2026-06-09 13:41:44 +00:00
alfresco-build 274278dbeb [release] 26.2.0-A.18
Update upstream versions
    - alfresco-community-repo:   26.2.0.60
    - alfresco-enterprise-share: 26.2.0.37
    - acs-packaging:             26.2.0-A.18
    - RELEASE_VERSION:           26.2.0-A.18
    - DEVELOPMENT_VERSION:       26.2.0-A.19-SNAPSHOT
2026-06-07 01:55:17 +00:00
tathagta15 fa2868c092 [ACS-11817] Bump IE/SS to 2.0.20 (#1679) 2026-06-04 13:00:04 +05:30
alfresco-build 2d4a251226 [maven-release-plugin][skip ci] prepare for next development iteration 2026-05-31 03:05:27 +00:00
alfresco-build 157e5a2553 [maven-release-plugin][skip ci] prepare release 26.2.0-A.17 2026-05-31 03:05:24 +00:00
alfresco-build 2b6cc0d8b2 [release] 26.2.0-A.17
Update upstream versions
    - alfresco-community-repo:   26.2.0.57
    - alfresco-enterprise-share: 26.2.0.34
    - acs-packaging:             26.2.0-A.17
    - RELEASE_VERSION:           26.2.0-A.17
    - DEVELOPMENT_VERSION:       26.2.0-A.18-SNAPSHOT
2026-05-31 01:57:11 +00:00
tathagta15 1f36d3eeb5 Bump Insight Engine to 2.0.20-A.2 (#1677) 2026-05-25 15:12:33 +05:30
alfresco-build ddcfbf0510 [release] 26.2.0-A.16
Update upstream versions
    - alfresco-community-repo:   26.2.0.54
    - alfresco-enterprise-share: 26.2.0.32
    - acs-packaging:             26.2.0-A.16
    - RELEASE_VERSION:           26.2.0-A.16
    - DEVELOPMENT_VERSION:       26.2.0-A.17-SNAPSHOT
2026-05-24 01:52:38 +00:00
alfresco-build 1175ea568b [release] 26.2.0-A.15
Update upstream versions
    - alfresco-community-repo:   26.2.0.49
    - alfresco-enterprise-share: 26.2.0.28
    - acs-packaging:             26.2.0-A.15
    - RELEASE_VERSION:           26.2.0-A.15
    - DEVELOPMENT_VERSION:       26.2.0-A.16-SNAPSHOT
2026-05-17 01:57:25 +00:00
alfresco-build c638d28cba [maven-release-plugin][skip ci] prepare for next development iteration 2026-05-10 03:04:44 +00:00
alfresco-build 56db329fc4 [maven-release-plugin][skip ci] prepare release 26.2.0-A.14 2026-05-10 03:04:42 +00:00
alfresco-build e1c2d88d2b [release] 26.2.0-A.14
Update upstream versions
    - alfresco-community-repo:   26.2.0.45
    - alfresco-enterprise-share: 26.2.0.26
    - acs-packaging:             26.2.0-A.14
    - RELEASE_VERSION:           26.2.0-A.14
    - DEVELOPMENT_VERSION:       26.2.0-A.15-SNAPSHOT
2026-05-10 01:58:54 +00:00
alfresco-build 4e2eec974d [maven-release-plugin][skip ci] prepare for next development iteration 2026-05-05 13:02:46 +00:00
alfresco-build a651c90dd4 [maven-release-plugin][skip ci] prepare release 26.2.0-A.13 2026-05-05 13:02:43 +00:00
alfresco-build 04b717ba95 [release] 26.2.0-A.13
Update upstream versions
    - alfresco-community-repo:   26.2.0.44
    - alfresco-enterprise-share: 26.2.0.24
    - acs-packaging:             26.2.0-A.13
    - RELEASE_VERSION:           26.2.0-A.13
    - DEVELOPMENT_VERSION:       26.2.0-A.14-SNAPSHOT
2026-05-05 11:55:58 +00:00
alfresco-build 72338d4243 [maven-release-plugin][skip ci] prepare for next development iteration 2026-05-03 02:58:22 +00:00
alfresco-build 9ed1edf334 [maven-release-plugin][skip ci] prepare release 26.2.0-A.12 2026-05-03 02:58:19 +00:00
alfresco-build 240071c9be [release] 26.2.0-A.12
Update upstream versions
    - alfresco-community-repo:   26.2.0.44
    - alfresco-enterprise-share: 26.2.0.23
    - acs-packaging:             26.2.0-A.12
    - RELEASE_VERSION:           26.2.0-A.12
    - DEVELOPMENT_VERSION:       26.2.0-A.13-SNAPSHOT
2026-05-03 01:49:13 +00:00
alfresco-build 0b715d5596 [maven-release-plugin][skip ci] prepare for next development iteration 2026-04-26 02:52:26 +00:00
alfresco-build 5e8864c4ba [maven-release-plugin][skip ci] prepare release 26.2.0-A.10 2026-04-26 02:52:24 +00:00
alfresco-build a72ac09968 [release] 26.2.0-A.10
Update upstream versions
    - alfresco-community-repo:   26.2.0.38
    - alfresco-enterprise-share: 26.2.0.19
    - acs-packaging:             26.2.0-A.10
    - RELEASE_VERSION:           26.2.0-A.10
    - DEVELOPMENT_VERSION:       26.2.0-A.11-SNAPSHOT
2026-04-26 01:46:25 +00:00
alfresco-build ce7197cc08 [maven-release-plugin][skip ci] prepare for next development iteration 2026-04-22 14:15:28 +00:00
alfresco-build 0a92a00e4f [maven-release-plugin][skip ci] prepare release 26.2.0-A.9 2026-04-22 14:15:26 +00:00
alfresco-build 12774a7d22 [release] 26.2.0-A.9
Update upstream versions
    - alfresco-community-repo:   26.2.0.36
    - alfresco-enterprise-share: 26.2.0.15
    - acs-packaging:             26.2.0-A.9
    - RELEASE_VERSION:           26.2.0-A.9
    - DEVELOPMENT_VERSION:       26.2.0-A.10-SNAPSHOT
2026-04-22 13:04:48 +00:00
alfresco-build db91c6c4d1 [maven-release-plugin][skip ci] prepare for next development iteration 2026-04-20 20:30:09 +00:00
alfresco-build 9a9839bf4d [maven-release-plugin][skip ci] prepare release 26.2.0-A.8 2026-04-20 20:30:07 +00:00
alfresco-build e595960521 [release] 26.2.0-A.8
Update upstream versions
    - alfresco-community-repo:   26.2.0.35
    - alfresco-enterprise-share: 26.2.0.12
    - acs-packaging:             26.2.0-A.8
    - RELEASE_VERSION:           26.2.0-A.8
    - DEVELOPMENT_VERSION:       26.2.0-A.9-SNAPSHOT
2026-04-20 19:17:55 +00:00
Grzegorz Oleksy e4f5ce6ee6 [ACS-11507] Bump IE to 2.0.19 (#1676) 2026-04-20 08:24:24 +02:00
alfresco-build b93a4418cb [maven-release-plugin][skip ci] prepare for next development iteration 2026-04-19 02:57:49 +00:00
alfresco-build e8b295f679 [maven-release-plugin][skip ci] prepare release 26.2.0-A.7 2026-04-19 02:57:48 +00:00
alfresco-build ed6dd74864 [release] 26.2.0-A.7
Update upstream versions
    - alfresco-community-repo:   26.2.0.33
    - alfresco-enterprise-share: 26.2.0.11
    - acs-packaging:             26.2.0-A.7
    - RELEASE_VERSION:           26.2.0-A.7
    - DEVELOPMENT_VERSION:       26.2.0-A.8-SNAPSHOT
2026-04-19 01:46:21 +00:00
alfresco-build b556a55117 [maven-release-plugin][skip ci] prepare for next development iteration 2026-04-12 02:51:05 +00:00
alfresco-build aeb1e994d2 [maven-release-plugin][skip ci] prepare release 26.2.0-A.6 2026-04-12 02:51:04 +00:00
alfresco-build f14138bbec [release] 26.2.0-A.6
Update upstream versions
    - alfresco-community-repo:   26.2.0.30
    - alfresco-enterprise-share: 26.2.0.8
    - acs-packaging:             26.2.0-A.6
    - RELEASE_VERSION:           26.2.0-A.6
    - DEVELOPMENT_VERSION:       26.2.0-A.7-SNAPSHOT
2026-04-12 01:42:01 +00:00
Grzegorz Oleksy 99102b597e [ACS-11508] Bump IE to 2.0.19-A.1 (#1675) 2026-04-11 06:44:40 +02:00
alfresco-build 4d7c1964ec [release] 26.2.0-A.5
Update upstream versions
    - alfresco-community-repo:   26.2.0.27
    - alfresco-enterprise-share: 26.2.0.6
    - acs-packaging:             26.2.0-A.5
    - RELEASE_VERSION:           26.2.0-A.5
    - DEVELOPMENT_VERSION:       26.2.0-A.6-SNAPSHOT
2026-04-09 08:38:32 +00:00
alfresco-build 19bcf20933 [maven-release-plugin][skip ci] prepare for next development iteration 2026-04-02 09:45:48 +00:00
alfresco-build 35f280eae5 [maven-release-plugin][skip ci] prepare release 26.2.0-A.4 2026-04-02 09:45:47 +00:00
alfresco-build dc0e49706e [release] 26.2.0-A.4
Update upstream versions
    - alfresco-community-repo:   26.2.0.25
    - alfresco-enterprise-share: 26.2.0.4
    - acs-packaging:             26.2.0-A.4
    - RELEASE_VERSION:           26.2.0-A.4
    - DEVELOPMENT_VERSION:       26.2.0-A.5-SNAPSHOT
2026-04-02 08:38:58 +00:00
alfresco-build dff2e08dd2 [release] 26.2.0-A.3
Update upstream versions
    - alfresco-community-repo:   26.2.0.20
    - alfresco-enterprise-share: 26.2.0.3
    - acs-packaging:             26.2.0-A.3
    - RELEASE_VERSION:           26.2.0-A.3
    - DEVELOPMENT_VERSION:       26.2.0-A.4-SNAPSHOT
2026-03-29 01:41:44 +00:00
alfresco-build 7b18ea984a [maven-release-plugin][skip ci] prepare for next development iteration 2026-03-22 02:44:48 +00:00
alfresco-build 5b208509a0 [maven-release-plugin][skip ci] prepare release 26.2.0-A.2 2026-03-22 02:44:47 +00:00
alfresco-build a2061e0731 [release] 26.2.0-A.2
Update upstream versions
    - alfresco-community-repo:   26.2.0.13
    - alfresco-enterprise-share: 26.2.0.2
    - acs-packaging:             26.2.0-A.2
    - RELEASE_VERSION:           26.2.0-A.2
    - DEVELOPMENT_VERSION:       26.2.0-A.3-SNAPSHOT
2026-03-22 01:39:45 +00:00
alfresco-build 267f9f988e [release] 26.2.0-A.1
Update upstream versions
    - alfresco-community-repo:   26.2.0.4
    - alfresco-enterprise-share: 26.2.0.1
    - acs-packaging:             26.2.0-A.1
    - RELEASE_VERSION:           26.2.0-A.1
    - DEVELOPMENT_VERSION:       26.2.0-A.2-SNAPSHOT
2026-03-15 01:40:47 +00:00
Kacper Magdziarz f00e8f014b [ACS-11384] Disable dependabot version upgrades for maven ecosystem and add grouping for GHA (#1673) 2026-03-12 13:03:27 +01:00
Jakub Kochman 6ae6140e28 Updating master branch to 26.2.0 after 26.1.0 ACS release [skip ci] 2026-03-11 19:07:33 +01:00
alfresco-build a7b2090893 [maven-release-plugin][skip ci] prepare for next development iteration 2026-03-11 15:06:36 +00:00
alfresco-build c58dd8bd50 [maven-release-plugin][skip ci] prepare release 26.1.0 2026-03-11 15:06:34 +00:00
alfresco-build 09eacc4294 [release][publish] 26.1.0
Update upstream versions
    - alfresco-community-repo:   26.1.0.61
    - alfresco-enterprise-share: 26.1.0.45
    - acs-packaging:             26.1.0
    - RELEASE_VERSION:           26.1.0
    - DEVELOPMENT_VERSION:       26.2.0-A.1-SNAPSHOT
2026-03-11 13:52:17 +00:00
alfresco-build 9bd65dbc7e [maven-release-plugin][skip ci] prepare for next development iteration 2026-03-08 02:50:28 +00:00
alfresco-build 555c3db905 [maven-release-plugin][skip ci] prepare release 26.1.0-A.22 2026-03-08 02:50:27 +00:00
alfresco-build 350b5fb159 [release] 26.1.0-A.22
Update upstream versions
    - alfresco-community-repo:   26.1.0.59
    - alfresco-enterprise-share: 26.1.0.42
    - acs-packaging:             26.1.0-A.22
    - RELEASE_VERSION:           26.1.0-A.22
    - DEVELOPMENT_VERSION:       26.1.0-A.23-SNAPSHOT
2026-03-08 01:37:44 +00:00
Damian Ujma 5c45fe3faa ACS-11178 Bump IE/SS to 2.0.18 (#1667) 2026-03-06 13:32:52 +01:00
alfresco-build b1ee5b8014 [release] 26.1.0-A.21
Update upstream versions
    - alfresco-community-repo:   26.1.0.57
    - alfresco-enterprise-share: 26.1.0.41
    - acs-packaging:             26.1.0-A.21
    - RELEASE_VERSION:           26.1.0-A.21
    - DEVELOPMENT_VERSION:       26.1.0-A.22-SNAPSHOT
2026-03-04 12:36:32 +00:00
alfresco-build 1ce3f6c150 [maven-release-plugin][skip ci] prepare for next development iteration 2026-03-03 16:19:25 +00:00
alfresco-build ae5b3fa58a [maven-release-plugin][skip ci] prepare release 26.1.0-A.20 2026-03-03 16:19:23 +00:00
alfresco-build 853e1a63bf [release] 26.1.0-A.20
Update upstream versions
    - alfresco-community-repo:   26.1.0.56
    - alfresco-enterprise-share: 26.1.0.40
    - acs-packaging:             26.1.0-A.20
    - RELEASE_VERSION:           26.1.0-A.20
    - DEVELOPMENT_VERSION:       26.1.0-A.21-SNAPSHOT
2026-03-03 15:07:50 +00:00
alfresco-build bb0854c36e [release] 26.1.0-A.19
Update upstream versions
    - alfresco-community-repo:   26.1.0.55
    - alfresco-enterprise-share: 26.1.0.39
    - acs-packaging:             26.1.0-A.19
    - RELEASE_VERSION:           26.1.0-A.19
    - DEVELOPMENT_VERSION:       26.1.0-A.20-SNAPSHOT
2026-03-01 01:38:08 +00:00
alfresco-build 0b295bd3f7 [release] 26.1.0-A.18
Update upstream versions
    - alfresco-community-repo:   26.1.0.53
    - alfresco-enterprise-share: 26.1.0.35
    - acs-packaging:             26.1.0-A.18
    - RELEASE_VERSION:           26.1.0-A.18
    - DEVELOPMENT_VERSION:       26.1.0-A.19-SNAPSHOT
2026-02-27 09:48:39 +00:00
Damian Ujma 08d8eb19d5 ACS-11179 Bump IE/SS to 2.0.18-A.4 (#1660) 2026-02-26 13:47:20 +01:00
alfresco-build ab6bcf90d1 [release] 26.1.0-A.17
Update upstream versions
    - alfresco-community-repo:   26.1.0.48
    - alfresco-enterprise-share: 26.1.0.32
    - acs-packaging:             26.1.0-A.17
    - RELEASE_VERSION:           26.1.0-A.17
    - DEVELOPMENT_VERSION:       26.1.0-A.18-SNAPSHOT
2026-02-25 11:25:19 +00:00
alfresco-build 9fb8af9192 [release] 26.1.0-A.16
Update upstream versions
    - alfresco-community-repo:   26.1.0.46
    - alfresco-enterprise-share: 26.1.0.30
    - acs-packaging:             26.1.0-A.16
    - RELEASE_VERSION:           26.1.0-A.16
    - DEVELOPMENT_VERSION:       26.1.0-A.17-SNAPSHOT
2026-02-22 01:38:58 +00:00
alfresco-build 0e05048b79 [maven-release-plugin][skip ci] prepare for next development iteration 2026-02-15 02:50:19 +00:00
alfresco-build ef6ae66a60 [maven-release-plugin][skip ci] prepare release 26.1.0-A.14 2026-02-15 02:50:17 +00:00
alfresco-build b37e4405a3 [release] 26.1.0-A.14
Update upstream versions
    - alfresco-community-repo:   26.1.0.45
    - alfresco-enterprise-share: 26.1.0.26
    - acs-packaging:             26.1.0-A.14
    - RELEASE_VERSION:           26.1.0-A.14
    - DEVELOPMENT_VERSION:       26.1.0-A.15-SNAPSHOT
2026-02-15 01:44:37 +00:00
alfresco-build 1451d7ef7a [maven-release-plugin][skip ci] prepare for next development iteration 2026-02-08 02:50:29 +00:00
alfresco-build bda880ca42 [maven-release-plugin][skip ci] prepare release 26.1.0-A.13 2026-02-08 02:50:27 +00:00
alfresco-build 55193a915b [release] 26.1.0-A.13
Update upstream versions
    - alfresco-community-repo:   26.1.0.41
    - alfresco-enterprise-share: 26.1.0.22
    - acs-packaging:             26.1.0-A.13
    - RELEASE_VERSION:           26.1.0-A.13
    - DEVELOPMENT_VERSION:       26.1.0-A.14-SNAPSHOT
2026-02-08 01:41:02 +00:00
alfresco-build 4e564a942a [release] 26.1.0-A.12
Update upstream versions
    - alfresco-community-repo:   26.1.0.38
    - alfresco-enterprise-share: 26.1.0.21
    - acs-packaging:             26.1.0-A.12
    - RELEASE_VERSION:           26.1.0-A.12
    - DEVELOPMENT_VERSION:       26.1.0-A.13-SNAPSHOT
2026-02-01 01:36:09 +00:00
alfresco-build f80bfa17b6 [release] 26.1.0-M.2
Update upstream versions
    - alfresco-community-repo:   26.1.0.37
    - alfresco-enterprise-share: 26.1.0.20
    - acs-packaging:             26.1.0-M.2
    - RELEASE_VERSION:           26.1.0-M.2
    - DEVELOPMENT_VERSION:       26.1.0-A.12-SNAPSHOT
2026-01-30 12:31:11 +00:00
alfresco-build 776e79135a [maven-release-plugin][skip ci] prepare for next development iteration 2026-01-29 12:44:14 +00:00
alfresco-build 20cb9e856b [maven-release-plugin][skip ci] prepare release 26.1.0-M.1 2026-01-29 12:44:12 +00:00
alfresco-build 5f8717652c [release] 26.1.0-M.1
Update upstream versions
    - alfresco-community-repo:   26.1.0.37
    - alfresco-enterprise-share: 26.1.0.20
    - acs-packaging:             26.1.0-M.1
    - RELEASE_VERSION:           26.1.0-M.1
    - DEVELOPMENT_VERSION:       26.1.0-A.12-SNAPSHOT
2026-01-29 11:40:46 +00:00
alfresco-build ee689aba90 [maven-release-plugin][skip ci] prepare for next development iteration 2026-01-25 02:45:21 +00:00
alfresco-build 46eac6d5b9 [maven-release-plugin][skip ci] prepare release 26.1.0-A.11 2026-01-25 02:45:20 +00:00
alfresco-build 351816775d [release] 26.1.0-A.11
Update upstream versions
    - alfresco-community-repo:   26.1.0.35
    - alfresco-enterprise-share: 26.1.0.20
    - acs-packaging:             26.1.0-A.11
    - RELEASE_VERSION:           26.1.0-A.11
    - DEVELOPMENT_VERSION:       26.1.0-A.12-SNAPSHOT
2026-01-25 01:40:07 +00:00
alfresco-build a0f498e8d2 [maven-release-plugin][skip ci] prepare for next development iteration 2026-01-21 15:12:28 +00:00
alfresco-build c8489fe6c7 [maven-release-plugin][skip ci] prepare release 26.1.0-A.10 2026-01-21 15:12:26 +00:00
alfresco-build d3aeac4a9d [release] 26.1.0-A.10
Update upstream versions
    - alfresco-community-repo:   26.1.0.34
    - alfresco-enterprise-share: 26.1.0.19
    - acs-packaging:             26.1.0-A.10
    - RELEASE_VERSION:           26.1.0-A.10
    - DEVELOPMENT_VERSION:       26.1.0-A.11-SNAPSHOT
2026-01-21 14:03:13 +00:00
alfresco-build 2b61e37be3 [maven-release-plugin][skip ci] prepare for next development iteration 2026-01-20 16:54:01 +00:00
alfresco-build 262573c6b7 [maven-release-plugin][skip ci] prepare release 26.1.0-A.9 2026-01-20 16:53:59 +00:00
alfresco-build 969683c33b [release] 26.1.0-A.9
Update upstream versions
    - alfresco-community-repo:   26.1.0.34
    - alfresco-enterprise-share: 26.1.0.18
    - acs-packaging:             26.1.0-A.9
    - RELEASE_VERSION:           26.1.0-A.9
    - DEVELOPMENT_VERSION:       26.1.0-A.10-SNAPSHOT
2026-01-20 15:44:41 +00:00
alfresco-build ba3fb7eec4 [release] 26.1.0-A.8
Update upstream versions
    - alfresco-community-repo:   26.1.0.33
    - alfresco-enterprise-share: 26.1.0.17
    - acs-packaging:             26.1.0-A.8
    - RELEASE_VERSION:           26.1.0-A.8
    - DEVELOPMENT_VERSION:       26.1.0-A.9-SNAPSHOT
2026-01-18 01:34:52 +00:00
alfresco-build 011e41a20d [release] 26.1.0-A.7
Update upstream versions
    - alfresco-community-repo:   26.1.0.23
    - alfresco-enterprise-share: 26.1.0.14
    - acs-packaging:             26.1.0-A.7
    - RELEASE_VERSION:           26.1.0-A.7
    - DEVELOPMENT_VERSION:       26.1.0-A.8-SNAPSHOT
2026-01-11 01:28:57 +00:00
alfresco-build 8736aea464 [maven-release-plugin][skip ci] prepare for next development iteration 2025-12-28 02:40:53 +00:00
alfresco-build c79250581c [maven-release-plugin][skip ci] prepare release 26.1.0-A.6 2025-12-28 02:40:51 +00:00
alfresco-build d4d2a5b15b [release] 26.1.0-A.6
Update upstream versions
    - alfresco-community-repo:   26.1.0.19
    - alfresco-enterprise-share: 26.1.0.12
    - acs-packaging:             26.1.0-A.6
    - RELEASE_VERSION:           26.1.0-A.6
    - DEVELOPMENT_VERSION:       26.1.0-A.7-SNAPSHOT
2025-12-28 01:30:11 +00:00
alfresco-build ed0008c5cd [maven-release-plugin][skip ci] prepare for next development iteration 2025-12-21 02:35:32 +00:00
alfresco-build 9cce32fc1f [maven-release-plugin][skip ci] prepare release 26.1.0-A.5 2025-12-21 02:35:31 +00:00
alfresco-build 751b1d4c14 [release] 26.1.0-A.5
Update upstream versions
    - alfresco-community-repo:   26.1.0.17
    - alfresco-enterprise-share: 26.1.0.11
    - acs-packaging:             26.1.0-A.5
    - RELEASE_VERSION:           26.1.0-A.5
    - DEVELOPMENT_VERSION:       26.1.0-A.6-SNAPSHOT
2025-12-21 01:30:45 +00:00
alfresco-build 3e404f7a3b [maven-release-plugin][skip ci] prepare for next development iteration 2025-12-11 11:10:42 +00:00
alfresco-build 023968ca5e [maven-release-plugin][skip ci] prepare release 26.1.0-A.4 2025-12-11 11:10:40 +00:00
Belal Ansari 673330dc5f Upgrade core ACS repositories to Java 21 [release] (#1658) (#1659) 2025-12-11 15:33:34 +05:30
alfresco-build 0ad1e5b653 [release] 26.1.0-A.4
Update upstream versions
    - alfresco-community-repo:   26.1.0.12
    - alfresco-enterprise-share: 26.1.0.9
    - acs-packaging:             26.1.0-A.4
    - RELEASE_VERSION:           26.1.0-A.4
    - DEVELOPMENT_VERSION:       26.1.0-A.5-SNAPSHOT
2025-12-10 10:14:42 +00:00
alfresco-build 1196038009 [maven-release-plugin][skip ci] prepare for next development iteration 2025-12-07 08:03:42 +00:00
alfresco-build 16589bfa17 [maven-release-plugin][skip ci] prepare release 26.1.0-A.3 2025-12-07 08:03:41 +00:00
alfresco-build be0264f5a2 [release] 26.1.0-A.3
Update upstream versions
    - alfresco-community-repo:   26.1.0.11
    - alfresco-enterprise-share: 26.1.0.8
    - acs-packaging:             26.1.0-A.3
    - RELEASE_VERSION:           26.1.0-A.3
    - DEVELOPMENT_VERSION:       26.1.0-A.4-SNAPSHOT
2025-12-07 01:29:28 +00:00
alfresco-build b36f5032e9 [maven-release-plugin][skip ci] prepare for next development iteration 2025-11-30 02:42:59 +00:00
alfresco-build d72064db50 [maven-release-plugin][skip ci] prepare release 26.1.0-A.2 2025-11-30 02:42:57 +00:00
alfresco-build df9d489142 [release] 26.1.0-A.2
Update upstream versions
    - alfresco-community-repo:   26.1.0.7
    - alfresco-enterprise-share: 26.1.0.6
    - acs-packaging:             26.1.0-A.2
    - RELEASE_VERSION:           26.1.0-A.2
    - DEVELOPMENT_VERSION:       26.1.0-A.3-SNAPSHOT
2025-11-30 01:31:42 +00:00
alfresco-build 5b0c9f3996 [maven-release-plugin][skip ci] prepare for next development iteration 2025-11-24 20:56:20 +00:00
alfresco-build 7731a4aa1a [maven-release-plugin][skip ci] prepare release 26.1.0-A.1 2025-11-24 20:56:19 +00:00
alfresco-build f082e00736 [release] 26.1.0-A.1
Update upstream versions
    - alfresco-community-repo:   26.1.0.1
    - alfresco-enterprise-share: 26.1.0.1
    - acs-packaging:             26.1.0-A.1
    - RELEASE_VERSION:           26.1.0-A.1
    - DEVELOPMENT_VERSION:       26.1.0-A.2-SNAPSHOT
2025-11-24 19:41:41 +00:00
Gerard Olenski 2928545f0b Updating master branch to 26.1.0 after 25.3.0 ACS release [skip ci] 2025-11-24 13:25:00 +01:00
alfresco-build de15826ca2 [maven-release-plugin][skip ci] prepare for next development iteration 2025-11-24 10:48:59 +00:00
alfresco-build a93f7e5dee [maven-release-plugin][skip ci] prepare release 25.3.0 2025-11-24 10:48:57 +00:00
alfresco-build 0e2388ce8a [release][publish] 25.3.0
Update upstream versions
    - alfresco-community-repo:   25.3.0.81
    - alfresco-enterprise-share: 25.3.0.56
    - acs-packaging:             25.3.0
    - RELEASE_VERSION:           25.3.0
    - DEVELOPMENT_VERSION:       26.1.0-A.1-SNAPSHOT
2025-11-24 09:39:51 +00:00
alfresco-build 37cdf77375 [maven-release-plugin][skip ci] prepare for next development iteration 2025-11-23 02:42:52 +00:00
alfresco-build 4b4a30a154 [maven-release-plugin][skip ci] prepare release 25.3.0-A.22 2025-11-23 02:42:50 +00:00
alfresco-build 782454e3fd [release] 25.3.0-A.22
Update upstream versions
    - alfresco-community-repo:   25.3.0.81
    - alfresco-enterprise-share: 25.3.0.56
    - acs-packaging:             25.3.0-A.22
    - RELEASE_VERSION:           25.3.0-A.22
    - DEVELOPMENT_VERSION:       25.3.0-A.23-SNAPSHOT
2025-11-23 01:33:15 +00:00
alfresco-build 29fa03aba0 [release] 25.3.0-A.21
Update upstream versions
    - alfresco-community-repo:   25.3.0.80
    - alfresco-enterprise-share: 25.3.0.55
    - acs-packaging:             25.3.0-A.21
    - RELEASE_VERSION:           25.3.0-A.21
    - DEVELOPMENT_VERSION:       25.3.0-A.22-SNAPSHOT
2025-11-22 06:30:16 +00:00
alfresco-build a3c4af776f [maven-release-plugin][skip ci] prepare for next development iteration 2025-11-20 23:50:21 +00:00
alfresco-build a33ef36bd6 [maven-release-plugin][skip ci] prepare release 25.3.0-A.20 2025-11-20 23:50:19 +00:00
alfresco-build ea3b050ed6 [release] 25.3.0-A.20
Update upstream versions
    - alfresco-community-repo:   25.3.0.78
    - alfresco-enterprise-share: 25.3.0.54
    - acs-packaging:             25.3.0-A.20
    - RELEASE_VERSION:           25.3.0-A.20
    - DEVELOPMENT_VERSION:       25.3.0-A.21-SNAPSHOT
2025-11-20 22:38:15 +00:00
alfresco-build e17e78a5d6 [release] 25.3.0-A.19
Update upstream versions
    - alfresco-community-repo:   25.3.0.74
    - alfresco-enterprise-share: 25.3.0.48
    - acs-packaging:             25.3.0-A.19
    - RELEASE_VERSION:           25.3.0-A.19
    - DEVELOPMENT_VERSION:       25.3.0-A.20-SNAPSHOT
2025-11-16 01:30:29 +00:00
alfresco-build 7f3a6133bc [release] 25.3.0-A.18
Update upstream versions
    - alfresco-community-repo:   25.3.0.72
    - alfresco-enterprise-share: 25.3.0.46
    - acs-packaging:             25.3.0-A.18
    - RELEASE_VERSION:           25.3.0-A.18
    - DEVELOPMENT_VERSION:       25.3.0-A.19-SNAPSHOT
2025-11-13 13:42:12 +00:00
alfresco-build 5240a04a68 [maven-release-plugin][skip ci] prepare for next development iteration 2025-11-09 15:15:20 +00:00
alfresco-build f13ca82499 [maven-release-plugin][skip ci] prepare release 25.3.0-A.17 2025-11-09 15:15:18 +00:00
alfresco-build 8133e5975c [release] 25.3.0-A.17
Update upstream versions
    - alfresco-community-repo:   25.3.0.72
    - alfresco-enterprise-share: 25.3.0.45
    - acs-packaging:             25.3.0-A.17
    - RELEASE_VERSION:           25.3.0-A.17
    - DEVELOPMENT_VERSION:       25.3.0-A.18-SNAPSHOT
2025-11-09 01:35:22 +00:00
alfresco-build 8cbc850861 [maven-release-plugin][skip ci] prepare for next development iteration 2025-11-04 13:58:02 +00:00
alfresco-build 8b3d450452 [maven-release-plugin][skip ci] prepare release 25.3.0-A.16 2025-11-04 13:58:00 +00:00
alfresco-build f7725392d9 [release] 25.3.0-A.16
Update upstream versions
    - alfresco-community-repo:   25.3.0.66
    - alfresco-enterprise-share: 25.3.0.41
    - acs-packaging:             25.3.0-A.16
    - RELEASE_VERSION:           25.3.0-A.16
    - DEVELOPMENT_VERSION:       25.3.0-A.17-SNAPSHOT
2025-11-02 01:51:49 +00:00
alfresco-build e44ce19320 [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-26 02:59:45 +00:00
alfresco-build 71ff372afc [maven-release-plugin][skip ci] prepare release 25.3.0-A.15 2025-10-26 02:59:43 +00:00
alfresco-build caea7b4efb [release] 25.3.0-A.15
Update upstream versions
    - alfresco-community-repo:   25.3.0.62
    - alfresco-enterprise-share: 25.3.0.36
    - acs-packaging:             25.3.0-A.15
    - RELEASE_VERSION:           25.3.0-A.15
    - DEVELOPMENT_VERSION:       25.3.0-A.16-SNAPSHOT
2025-10-26 01:45:49 +00:00
alfresco-build ac323556b5 [maven-release-plugin][skip ci] prepare for next development iteration 2025-10-13 08:42:15 +00:00
alfresco-build 421022a576 [maven-release-plugin][skip ci] prepare release 25.3.0-A.13 2025-10-13 08:42:13 +00:00
alfresco-build 2a0b98e423 [release] 25.3.0-A.13
Update upstream versions
    - alfresco-community-repo:   25.3.0.58
    - alfresco-enterprise-share: 25.3.0.32
    - acs-packaging:             25.3.0-A.13
    - RELEASE_VERSION:           25.3.0-A.13
    - DEVELOPMENT_VERSION:       25.3.0-A.14-SNAPSHOT
2025-10-13 07:29:06 +00:00
alfresco-build 4cc4adf61f [release] 25.3.0-A.12
Update upstream versions
    - alfresco-community-repo:   25.3.0.53
    - alfresco-enterprise-share: 25.3.0.29
    - acs-packaging:             25.3.0-A.12
    - RELEASE_VERSION:           25.3.0-A.12
    - DEVELOPMENT_VERSION:       25.3.0-A.13-SNAPSHOT
2025-10-07 08:29:02 +00:00
alfresco-build bc308f3615 [release] 25.3.0-A.11
Update upstream versions
    - alfresco-community-repo:   25.3.0.50
    - alfresco-enterprise-share: 25.3.0.28
    - acs-packaging:             25.3.0-A.11
    - RELEASE_VERSION:           25.3.0-A.11
    - DEVELOPMENT_VERSION:       25.3.0-A.12-SNAPSHOT
2025-09-28 01:37:41 +00:00
alfresco-build c5bf783bca [release] 25.3.0-A.10
Update upstream versions
    - alfresco-community-repo:   25.3.0.49
    - alfresco-enterprise-share: 25.3.0.26
    - acs-packaging:             25.3.0-A.10
    - RELEASE_VERSION:           25.3.0-A.10
    - DEVELOPMENT_VERSION:       25.3.0-A.11-SNAPSHOT
2025-09-26 14:16:57 +00:00
alfresco-build a0531edf72 [release] 25.3.0-A.9
Update upstream versions
    - alfresco-community-repo:   25.3.0.39
    - alfresco-enterprise-share: 25.3.0.26
    - acs-packaging:             25.3.0-A.9
    - RELEASE_VERSION:           25.3.0-A.9
    - DEVELOPMENT_VERSION:       25.3.0-A.10-SNAPSHOT
2025-09-21 01:34:29 +00:00
alfresco-build b85ebfa4c1 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-19 10:51:32 +00:00
alfresco-build 56ea91fa5b [maven-release-plugin][skip ci] prepare release 25.3.0-A.8 2025-09-19 10:51:30 +00:00
alfresco-build 0ec0dc3043 [release] 25.3.0-A.8
Update upstream versions
    - alfresco-community-repo:   25.3.0.38
    - alfresco-enterprise-share: 25.3.0.25
    - acs-packaging:             25.3.0-A.8
    - RELEASE_VERSION:           25.3.0-A.8
    - DEVELOPMENT_VERSION:       25.3.0-A.9-SNAPSHOT
2025-09-19 09:46:09 +00:00
alfresco-build 73acd243e8 [release] 25.3.0-A.7
Update upstream versions
    - alfresco-community-repo:   25.3.0.36
    - alfresco-enterprise-share: 25.3.0.24
    - acs-packaging:             25.3.0-A.7
    - RELEASE_VERSION:           25.3.0-A.7
    - DEVELOPMENT_VERSION:       25.3.0-A.8-SNAPSHOT
2025-09-14 01:34:24 +00:00
alfresco-build 5f339944a3 [release] 25.3.0-A.6
Update upstream versions
    - alfresco-community-repo:   25.3.0.34
    - alfresco-enterprise-share: 25.3.0.22
    - acs-packaging:             25.3.0-A.6
    - RELEASE_VERSION:           25.3.0-A.6
    - DEVELOPMENT_VERSION:       25.3.0-A.7-SNAPSHOT
2025-09-07 01:40:25 +00:00
alfresco-build 8872f9634a [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-24 02:24:44 +00:00
alfresco-build 7db6d945d6 [maven-release-plugin][skip ci] prepare release 25.3.0-A.5 2025-08-24 02:24:43 +00:00
alfresco-build 630b48ffdf [release] 25.3.0-A.5
Update upstream versions
    - alfresco-community-repo:   25.3.0.26
    - alfresco-enterprise-share: 25.3.0.14
    - acs-packaging:             25.3.0-A.5
    - RELEASE_VERSION:           25.3.0-A.5
    - DEVELOPMENT_VERSION:       25.3.0-A.6-SNAPSHOT
2025-08-24 01:24:07 +00:00
alfresco-build 36a99c4587 [release] 25.3.0-A.4
Update upstream versions
    - alfresco-community-repo:   25.3.0.24
    - alfresco-enterprise-share: 25.3.0.13
    - acs-packaging:             25.3.0-A.4
    - RELEASE_VERSION:           25.3.0-A.4
    - DEVELOPMENT_VERSION:       25.3.0-A.5-SNAPSHOT
2025-08-18 10:32:49 +00:00
alfresco-build 485270a3b2 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-17 02:33:31 +00:00
alfresco-build 962e79f052 [maven-release-plugin][skip ci] prepare release 25.3.0-A.3 2025-08-17 02:33:29 +00:00
alfresco-build af7ef7286b [release] 25.3.0-A.3
Update upstream versions
    - alfresco-community-repo:   25.3.0.24
    - alfresco-enterprise-share: 25.3.0.13
    - acs-packaging:             25.3.0-A.3
    - RELEASE_VERSION:           25.3.0-A.3
    - DEVELOPMENT_VERSION:       25.3.0-A.4-SNAPSHOT
2025-08-17 01:26:14 +00:00
alfresco-build e54a70ff32 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-10 02:25:21 +00:00
alfresco-build 7f949924c3 [maven-release-plugin][skip ci] prepare release 25.3.0-A.2 2025-08-10 02:25:19 +00:00
alfresco-build 0045df452f [release] 25.3.0-A.2
Update upstream versions
    - alfresco-community-repo:   25.3.0.21
    - alfresco-enterprise-share: 25.3.0.7
    - acs-packaging:             25.3.0-A.2
    - RELEASE_VERSION:           25.3.0-A.2
    - DEVELOPMENT_VERSION:       25.3.0-A.3-SNAPSHOT
2025-08-10 01:22:48 +00:00
alfresco-build 89fdb0acaf [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-07 13:21:26 +00:00
alfresco-build 50e3f88d97 [maven-release-plugin][skip ci] prepare release 25.3.0-A.1 2025-08-07 13:21:24 +00:00
alfresco-build 68d49a81f8 [release] 25.3.0-A.1
Update upstream versions
    - alfresco-community-repo:   25.3.0.17
    - alfresco-enterprise-share: 25.3.0.5
    - acs-packaging:             25.3.0-A.1
    - RELEASE_VERSION:           25.3.0-A.1
    - DEVELOPMENT_VERSION:       25.3.0-A.2-SNAPSHOT
2025-08-07 12:17:48 +00:00
alfresco-build 8ec63574a3 [maven-release-plugin][skip ci] prepare for next development iteration 2025-07-16 08:41:14 +00:00
alfresco-build 1031230f1f [maven-release-plugin][skip ci] prepare release 25.2.0 2025-07-16 08:41:12 +00:00
alfresco-build 6b3b16a9a4 [release][publish] 25.2.0
Update upstream versions
    - alfresco-community-repo:   25.2.0.64
    - alfresco-enterprise-share: 25.2.0.46
    - acs-packaging:             25.2.0
    - RELEASE_VERSION:           25.2.0
    - DEVELOPMENT_VERSION:       25.3.0-A.1-SNAPSHOT
2025-07-16 07:23:27 +00:00
alfresco-build 9892d3705a [release] 25.2.0-A.18
Update upstream versions
    - alfresco-community-repo:   25.2.0.61
    - alfresco-enterprise-share: 25.2.0.44
    - acs-packaging:             25.2.0-A.18
    - RELEASE_VERSION:           25.2.0-A.18
    - DEVELOPMENT_VERSION:       25.2.0-A.19-SNAPSHOT
2025-07-13 01:50:48 +00:00
alfresco-build 06ead549e3 [maven-release-plugin][skip ci] prepare for next development iteration 2025-07-06 02:53:27 +00:00
alfresco-build 360c584f64 [maven-release-plugin][skip ci] prepare release 25.2.0-A.17 2025-07-06 02:53:26 +00:00
alfresco-build 3bb5a988cf [release] 25.2.0-A.17
Update upstream versions
    - alfresco-community-repo:   25.2.0.60
    - alfresco-enterprise-share: 25.2.0.43
    - acs-packaging:             25.2.0-A.17
    - RELEASE_VERSION:           25.2.0-A.17
    - DEVELOPMENT_VERSION:       25.2.0-A.18-SNAPSHOT
2025-07-06 01:30:31 +00:00
tathagta15 3442fce59a [ACS-9821] Bump IE to 2.0.16-A.2 (#1653) 2025-07-04 10:14:58 +05:30
alfresco-build f83f55060a [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-29 02:39:25 +00:00
alfresco-build 1e8dd2817a [maven-release-plugin][skip ci] prepare release 25.2.0-A.16 2025-06-29 02:39:23 +00:00
alfresco-build 17a38d51d0 [release] 25.2.0-A.16
Update upstream versions
    - alfresco-community-repo:   25.2.0.54
    - alfresco-enterprise-share: 25.2.0.37
    - acs-packaging:             25.2.0-A.16
    - RELEASE_VERSION:           25.2.0-A.16
    - DEVELOPMENT_VERSION:       25.2.0-A.17-SNAPSHOT
2025-06-29 01:32:24 +00:00
tathagta15 25af309ea9 [ACS-9745] Removed reportPortal integration (#1652)
* ACS-9feature/ACS-9745-removed-reportPortal-integration

* Update pom.xml

updated the versions

* Update pom.xml

fixed versioning issue

* fixed versions of dependent repos

* Update pom.xml

synced all version with snapshots
2025-06-24 11:21:13 +05:30
alfresco-build c37e9526c6 [release] 25.2.0-A.15
Update upstream versions
    - alfresco-community-repo:   25.2.0.49
    - alfresco-enterprise-share: 25.2.0.36
    - acs-packaging:             25.2.0-A.15
    - RELEASE_VERSION:           25.2.0-A.15
    - DEVELOPMENT_VERSION:       25.2.0-A.16-SNAPSHOT
2025-06-23 08:51:38 +00:00
alfresco-build 29720e6e64 [release] 25.2.0-A.13
Update upstream versions
    - alfresco-community-repo:   25.2.0.42
    - alfresco-enterprise-share: 25.2.0.33
    - acs-packaging:             25.2.0-A.13
    - RELEASE_VERSION:           25.2.0-A.13
    - DEVELOPMENT_VERSION:       25.2.0-A.14-SNAPSHOT
2025-06-08 01:30:05 +00:00
alfresco-build 26fbca754f [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-01 02:54:07 +00:00
alfresco-build a04fbabee9 [maven-release-plugin][skip ci] prepare release 25.2.0-A.12 2025-06-01 02:54:05 +00:00
alfresco-build a142b96bdf [release] 25.2.0-A.12
Update upstream versions
    - alfresco-community-repo:   25.2.0.39
    - alfresco-enterprise-share: 25.2.0.30
    - acs-packaging:             25.2.0-A.12
    - RELEASE_VERSION:           25.2.0-A.12
    - DEVELOPMENT_VERSION:       25.2.0-A.13-SNAPSHOT
2025-06-01 01:44:38 +00:00
alfresco-build a3fb8d3637 [release] 25.2.0-A.11
Update upstream versions
    - alfresco-community-repo:   25.2.0.36
    - alfresco-enterprise-share: 25.2.0.28
    - acs-packaging:             25.2.0-A.11
    - RELEASE_VERSION:           25.2.0-A.11
    - DEVELOPMENT_VERSION:       25.2.0-A.12-SNAPSHOT
2025-05-25 01:29:43 +00:00
alfresco-build c19bcdb4fc [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-23 14:32:42 +00:00
alfresco-build b54d674d5b [maven-release-plugin][skip ci] prepare release 25.2.0-A.10 2025-05-23 14:32:40 +00:00
Piotr Żurek a60bde4b45 [release] 25.2.0-A.10 2025-05-23 14:47:05 +02:00
Piotr Żurek 82323f0a95 MNT-24358 Adjust distribiution test (#1651) 2025-05-23 14:45:26 +02:00
alfresco-build d44d995a10 [release] 25.2.0-A.10
Update upstream versions
    - alfresco-community-repo:   25.2.0.33
    - alfresco-enterprise-share: 25.2.0.25
    - acs-packaging:             25.2.0-A.10
    - RELEASE_VERSION:           25.2.0-A.10
    - DEVELOPMENT_VERSION:       25.2.0-A.11-SNAPSHOT
2025-05-19 21:39:21 +00:00
alfresco-build 87cb633a4b [release] 25.2.0-A.9
Update upstream versions
    - alfresco-community-repo:   25.2.0.26
    - alfresco-enterprise-share: 25.2.0.22
    - acs-packaging:             25.2.0-A.9
    - RELEASE_VERSION:           25.2.0-A.9
    - DEVELOPMENT_VERSION:       25.2.0-A.10-SNAPSHOT
2025-05-11 01:27:07 +00:00
alfresco-build d7bc35f807 [release] 25.2.0-A.8
Update upstream versions
    - alfresco-community-repo:   25.2.0.21
    - alfresco-enterprise-share: 25.2.0.18
    - acs-packaging:             25.2.0-A.8
    - RELEASE_VERSION:           25.2.0-A.8
    - DEVELOPMENT_VERSION:       25.2.0-A.9-SNAPSHOT
2025-05-04 01:35:09 +00:00
alfresco-build 2a93669740 [release] 25.2.0-A.7
Update upstream versions
    - alfresco-community-repo:   25.2.0.19
    - alfresco-enterprise-share: 25.2.0.17
    - acs-packaging:             25.2.0-A.7
    - RELEASE_VERSION:           25.2.0-A.7
    - DEVELOPMENT_VERSION:       25.2.0-A.8-SNAPSHOT
2025-04-27 01:42:35 +00:00
alfresco-build 174ab3349f [release] 25.2.0-A.6
Update upstream versions
    - alfresco-community-repo:   25.2.0.16
    - alfresco-enterprise-share: 25.2.0.15
    - acs-packaging:             25.2.0-A.6
    - RELEASE_VERSION:           25.2.0-A.6
    - DEVELOPMENT_VERSION:       25.2.0-A.7-SNAPSHOT
2025-04-20 01:57:04 +00:00
alfresco-build 999bfa4f46 [maven-release-plugin][skip ci] prepare for next development iteration 2025-04-13 02:52:03 +00:00
alfresco-build a719ab4fa9 [maven-release-plugin][skip ci] prepare release 25.2.0-A.5 2025-04-13 02:52:02 +00:00
alfresco-build dfe9b7a59e [release] 25.2.0-A.5
Update upstream versions
    - alfresco-community-repo:   25.2.0.13
    - alfresco-enterprise-share: 25.2.0.14
    - acs-packaging:             25.2.0-A.5
    - RELEASE_VERSION:           25.2.0-A.5
    - DEVELOPMENT_VERSION:       25.2.0-A.6-SNAPSHOT
2025-04-13 01:51:57 +00:00
alfresco-build e77852afea [maven-release-plugin][skip ci] prepare for next development iteration 2025-04-06 02:38:27 +00:00
alfresco-build f97f9a0a91 [maven-release-plugin][skip ci] prepare release 25.2.0-A.4 2025-04-06 02:38:25 +00:00
alfresco-build b9d9ae55d3 [release] 25.2.0-A.4
Update upstream versions
    - alfresco-community-repo:   25.2.0.10
    - alfresco-enterprise-share: 25.2.0.10
    - acs-packaging:             25.2.0-A.4
    - RELEASE_VERSION:           25.2.0-A.4
    - DEVELOPMENT_VERSION:       25.2.0-A.5-SNAPSHOT
2025-04-06 01:36:56 +00:00
alfresco-build 8dc54096e3 [release] 25.2.0-A.3
Update upstream versions
    - alfresco-community-repo:   25.2.0.7
    - alfresco-enterprise-share: 25.2.0.6
    - acs-packaging:             25.2.0-A.3
    - RELEASE_VERSION:           25.2.0-A.3
    - DEVELOPMENT_VERSION:       25.2.0-A.4-SNAPSHOT
2025-03-30 01:41:30 +00:00
alfresco-build 5b6c11b730 [maven-release-plugin][skip ci] prepare for next development iteration 2025-03-27 18:32:59 +00:00
alfresco-build c6b699eb64 [maven-release-plugin][skip ci] prepare release 25.2.0-A.2 2025-03-27 18:32:58 +00:00
alfresco-build b9a034a276 [release] 25.2.0-A.2
Update upstream versions
    - alfresco-community-repo:   25.2.0.5
    - alfresco-enterprise-share: 25.2.0.3
    - acs-packaging:             25.2.0-A.2
    - RELEASE_VERSION:           25.2.0-A.2
    - DEVELOPMENT_VERSION:       25.2.0-A.3-SNAPSHOT
2025-03-27 17:33:42 +00:00
alfresco-build ca4f8c68be [maven-release-plugin][skip ci] prepare for next development iteration 2025-03-23 02:26:36 +00:00
alfresco-build 140157277f [maven-release-plugin][skip ci] prepare release 25.2.0-A.1 2025-03-23 02:26:34 +00:00
alfresco-build 734a476ece [release] 25.2.0-A.1
Update upstream versions
    - alfresco-community-repo:   25.2.0.3
    - alfresco-enterprise-share: 25.2.0.2
    - acs-packaging:             25.2.0-A.1
    - RELEASE_VERSION:           25.2.0-A.1
    - DEVELOPMENT_VERSION:       25.2.0-A.2-SNAPSHOT
2025-03-23 01:26:23 +00:00
Gerard Olenskiandalfresco-build 6252de4707 ACS-9420 pre-commit checks and reformat code (#1650)
* ACS-9420 Reformat code and add pre-commit checks to CI

* Trigger [reformat code]

* Apply Pre-Commit code formatting

---------

Co-authored-by: alfresco-build <alfresco-build@users.noreply.github.com>
2025-03-19 10:05:05 +01:00
Kacper Magdziarz 3c34c532d0 Updating master branch to 25.2.0 after 25.1.0 ACS release [skip ci] 2025-03-17 13:47:09 +01:00
alfresco-build b1b5c9f660 [maven-release-plugin][skip ci] prepare for next development iteration 2025-03-17 12:12:47 +00:00
alfresco-build ba43305169 [maven-release-plugin][skip ci] prepare release 25.1.0 2025-03-17 12:12:40 +00:00
alfresco-build bd8d35fbb0 [release][publish] 25.1.0
Update upstream versions
    - alfresco-community-repo:   25.1.0.71
    - alfresco-enterprise-share: 25.1.0.56
    - acs-packaging:             25.1.0
    - RELEASE_VERSION:           25.1.0
    - DEVELOPMENT_VERSION:       25.2.0-A.1-SNAPSHOT
2025-03-17 11:12:00 +00:00
alfresco-build e4ac865301 [release] 25.1.0-A.20
Update upstream versions
    - alfresco-community-repo:   25.1.0.71
    - alfresco-enterprise-share: 25.1.0.56
    - acs-packaging:             25.1.0-A.20
    - RELEASE_VERSION:           25.1.0-A.20
    - DEVELOPMENT_VERSION:       25.1.0-A.21-SNAPSHOT
2025-03-16 01:50:11 +00:00
alfresco-build ce91803b7a [release] 25.1.0-A.19
Update upstream versions
    - alfresco-community-repo:   25.1.0.70
    - alfresco-enterprise-share: 25.1.0.55
    - acs-packaging:             25.1.0-A.19
    - RELEASE_VERSION:           25.1.0-A.19
    - DEVELOPMENT_VERSION:       25.1.0-A.20-SNAPSHOT
2025-03-13 18:06:23 +00:00
Damian Ujma adeab43b5a ACS-9357 Bump IE/SS to 2.0.15 (#1649) 2025-03-10 15:19:31 +01:00
Damian Ujma c8d47f10f6 ACS-9385 Bump alfresco-build-tools to v8.13.0 (#1648) 2025-03-10 10:21:54 +01:00
alfresco-build ff9c75bc55 [release] 25.1.0-A.18
Update upstream versions
    - alfresco-community-repo:   25.1.0.67
    - alfresco-enterprise-share: 25.1.0.52
    - acs-packaging:             25.1.0-A.18
    - RELEASE_VERSION:           25.1.0-A.18
    - DEVELOPMENT_VERSION:       25.1.0-A.19-SNAPSHOT
2025-03-09 01:29:17 +00:00
alfresco-build 24db2ecd16 [release] 25.1.0-A.17
Update upstream versions
    - alfresco-community-repo:   25.1.0.64
    - alfresco-enterprise-share: 25.1.0.50
    - acs-packaging:             25.1.0-A.17
    - RELEASE_VERSION:           25.1.0-A.17
    - DEVELOPMENT_VERSION:       25.1.0-A.18-SNAPSHOT
2025-03-07 07:17:09 +00:00
alfresco-build bea8523556 [release] 25.1.0-A.16
Update upstream versions
    - alfresco-community-repo:   25.1.0.64
    - alfresco-enterprise-share: 25.1.0.50
    - acs-packaging:             25.1.0-A.16
    - RELEASE_VERSION:           25.1.0-A.16
    - DEVELOPMENT_VERSION:       25.1.0-A.17-SNAPSHOT
2025-03-03 14:08:28 +00:00
alfresco-build 9c863ba2a0 [release] 25.1.0-A.15
Update upstream versions
    - alfresco-community-repo:   25.1.0.56
    - alfresco-enterprise-share: 25.1.0.39
    - acs-packaging:             25.1.0-A.15
    - RELEASE_VERSION:           25.1.0-A.15
    - DEVELOPMENT_VERSION:       25.1.0-A.16-SNAPSHOT
2025-02-24 14:20:28 +00:00
alfresco-build 63de82aea2 [release] 25.1.0-A.14
Update upstream versions
    - alfresco-community-repo:   25.1.0.56
    - alfresco-enterprise-share: 25.1.0.39
    - acs-packaging:             25.1.0-A.14
    - RELEASE_VERSION:           25.1.0-A.14
    - DEVELOPMENT_VERSION:       25.1.0-A.15-SNAPSHOT
2025-02-23 01:38:16 +00:00
Kacper Magdziarz 5d512ed857 [ACS-9270] Switch MySQL driver from com.mysql.jdbc.Driver to com.mysql.cj.jdbc.Driver (#1645) 2025-02-17 12:59:01 +01:00
alfresco-build bc9ec03949 [maven-release-plugin][skip ci] prepare for next development iteration 2025-02-16 02:40:59 +00:00
alfresco-build 495266b3d7 [maven-release-plugin][skip ci] prepare release 25.1.0-A.13 2025-02-16 02:40:58 +00:00
alfresco-build fbb9ef7e16 [release] 25.1.0-A.13
Update upstream versions
    - alfresco-community-repo:   25.1.0.51
    - alfresco-enterprise-share: 25.1.0.38
    - acs-packaging:             25.1.0-A.13
    - RELEASE_VERSION:           25.1.0-A.13
    - DEVELOPMENT_VERSION:       25.1.0-A.14-SNAPSHOT
2025-02-16 01:42:45 +00:00
alfresco-build 4a8d7cf5b2 [maven-release-plugin][skip ci] prepare for next development iteration 2025-02-09 02:59:03 +00:00
alfresco-build 9345081327 [maven-release-plugin][skip ci] prepare release 25.1.0-A.12 2025-02-09 02:59:02 +00:00
alfresco-build 9d810abaa2 [release] 25.1.0-A.12
Update upstream versions
    - alfresco-community-repo:   25.1.0.47
    - alfresco-enterprise-share: 25.1.0.35
    - acs-packaging:             25.1.0-A.12
    - RELEASE_VERSION:           25.1.0-A.12
    - DEVELOPMENT_VERSION:       25.1.0-A.13-SNAPSHOT
2025-02-09 02:03:36 +00:00
alfresco-build e07f6f2f60 [maven-release-plugin][skip ci] prepare for next development iteration 2025-02-02 02:26:19 +00:00
alfresco-build be5b82fc1c [maven-release-plugin][skip ci] prepare release 25.1.0-A.11 2025-02-02 02:26:17 +00:00
alfresco-build 8487068b03 [release] 25.1.0-A.11
Update upstream versions
    - alfresco-community-repo:   25.1.0.43
    - alfresco-enterprise-share: 25.1.0.31
    - acs-packaging:             25.1.0-A.11
    - RELEASE_VERSION:           25.1.0-A.11
    - DEVELOPMENT_VERSION:       25.1.0-A.12-SNAPSHOT
2025-02-02 01:30:27 +00:00
alfresco-build 9764e292a9 [maven-release-plugin][skip ci] prepare for next development iteration 2025-01-28 16:10:46 +00:00
alfresco-build 79ca33f16b [maven-release-plugin][skip ci] prepare release 25.1.0-M1 2025-01-28 16:10:44 +00:00
alfresco-build e61f0e6acd [release] 25.1.0-M1
Update upstream versions
    - alfresco-community-repo:   25.1.0.37
    - alfresco-enterprise-share: 25.1.0.29
    - acs-packaging:             25.1.0-M1
    - RELEASE_VERSION:           25.1.0-M1
    - DEVELOPMENT_VERSION:       25.1.0-A.11-SNAPSHOT
2025-01-28 15:12:53 +00:00
alfresco-build 62c11b4ba3 [maven-release-plugin][skip ci] prepare for next development iteration 2025-01-26 02:23:55 +00:00
alfresco-build 14eb024001 [maven-release-plugin][skip ci] prepare release 25.1.0-A.10 2025-01-26 02:23:53 +00:00
alfresco-build d7880064d5 [release] 25.1.0-A.10
Update upstream versions
    - alfresco-community-repo:   25.1.0.36
    - alfresco-enterprise-share: 25.1.0.28
    - acs-packaging:             25.1.0-A.10
    - RELEASE_VERSION:           25.1.0-A.10
    - DEVELOPMENT_VERSION:       25.1.0-A.11-SNAPSHOT
2025-01-26 01:25:27 +00:00
alfresco-build 10c9b11f21 [maven-release-plugin][skip ci] prepare for next development iteration 2025-01-21 07:44:59 +00:00
alfresco-build 8c096b7203 [maven-release-plugin][skip ci] prepare release 25.1.0-A.9 2025-01-21 07:44:57 +00:00
alfresco-build ccc1ff32b0 [release] 25.1.0-A.9
Update upstream versions
    - alfresco-community-repo:   25.1.0.35
    - alfresco-enterprise-share: 25.1.0.27
    - acs-packaging:             25.1.0-A.9
    - RELEASE_VERSION:           25.1.0-A.9
    - DEVELOPMENT_VERSION:       25.1.0-A.10-SNAPSHOT
2025-01-21 06:48:58 +00:00
Damian Ujma e85aa18487 ACS-9140 Bump IE/SS to 2.0.14-A.1 (#1643) 2025-01-16 16:49:45 +01:00
alfresco-build bd41276881 [maven-release-plugin][skip ci] prepare for next development iteration 2025-01-12 02:52:45 +00:00
alfresco-build 34a495a878 [maven-release-plugin][skip ci] prepare release 25.1.0-A.7 2025-01-12 02:52:42 +00:00
alfresco-build a08cf71ca7 [release] 25.1.0-A.7
Update upstream versions
    - alfresco-community-repo:   25.1.0.27
    - alfresco-enterprise-share: 25.1.0.20
    - acs-packaging:             25.1.0-A.7
    - RELEASE_VERSION:           25.1.0-A.7
    - DEVELOPMENT_VERSION:       25.1.0-A.8-SNAPSHOT
2025-01-12 01:27:07 +00:00
alfresco-build e2d23cda41 [release] 25.1.0-A.6
Update upstream versions
    - alfresco-community-repo:   25.1.0.20
    - alfresco-enterprise-share: 25.1.0.17
    - acs-packaging:             25.1.0-A.6
    - RELEASE_VERSION:           25.1.0-A.6
    - DEVELOPMENT_VERSION:       25.1.0-A.7-SNAPSHOT
2024-12-29 01:31:50 +00:00
alfresco-build d9a26c4f04 [release] 25.1.0-A.5
Update upstream versions
    - alfresco-community-repo:   25.1.0.18
    - alfresco-enterprise-share: 25.1.0.15
    - acs-packaging:             25.1.0-A.5
    - RELEASE_VERSION:           25.1.0-A.5
    - DEVELOPMENT_VERSION:       25.1.0-A.6-SNAPSHOT
2024-12-22 01:34:44 +00:00
alfresco-build 42449efc05 [maven-release-plugin][skip ci] prepare for next development iteration 2024-12-15 02:24:32 +00:00
alfresco-build 3c72804cb2 [maven-release-plugin][skip ci] prepare release 25.1.0-A.4 2024-12-15 02:24:31 +00:00
alfresco-build 2a1618eed1 [release] 25.1.0-A.4
Update upstream versions
    - alfresco-community-repo:   25.1.0.16
    - alfresco-enterprise-share: 25.1.0.14
    - acs-packaging:             25.1.0-A.4
    - RELEASE_VERSION:           25.1.0-A.4
    - DEVELOPMENT_VERSION:       25.1.0-A.5-SNAPSHOT
2024-12-15 01:28:13 +00:00
alfresco-build 8dae776c87 [release] 25.1.0-A.3
Update upstream versions
    - alfresco-community-repo:   25.1.0.11
    - alfresco-enterprise-share: 25.1.0.10
    - acs-packaging:             25.1.0-A.3
    - RELEASE_VERSION:           25.1.0-A.3
    - DEVELOPMENT_VERSION:       25.1.0-A.4-SNAPSHOT
2024-12-08 01:31:51 +00:00
alfresco-build a7e3eb01fb [release] 25.1.0-A.2
Update upstream versions
    - alfresco-community-repo:   25.1.0.7
    - alfresco-enterprise-share: 25.1.0.7
    - acs-packaging:             25.1.0-A.2
    - RELEASE_VERSION:           25.1.0-A.2
    - DEVELOPMENT_VERSION:       25.1.0-A.3-SNAPSHOT
2024-12-01 01:31:03 +00:00
alfresco-build 1db23f2e7d [maven-release-plugin][skip ci] prepare for next development iteration 2024-11-27 21:24:28 +00:00
alfresco-build d917a6856f [maven-release-plugin][skip ci] prepare release 25.1.0-A.1 2024-11-27 21:24:27 +00:00
alfresco-build aa086100c4 [release] 25.1.0-A.1
Update upstream versions
    - alfresco-community-repo:   25.1.0.3
    - alfresco-enterprise-share: 25.1.0.4
    - acs-packaging:             25.1.0-A.1
    - RELEASE_VERSION:           25.1.0-A.1
    - DEVELOPMENT_VERSION:       25.1.0-A.2-SNAPSHOT
2024-11-27 20:14:42 +00:00
alfresco-build a28f7a2e4e [maven-release-plugin][skip ci] prepare for next development iteration 2024-11-20 12:20:20 +00:00
alfresco-build f46a4e15bc [maven-release-plugin][skip ci] prepare release 23.4.0 2024-11-20 12:20:18 +00:00
alfresco-build ecdfd47ce7 [release][publish] 23.4.0
Update upstream versions
    - alfresco-community-repo:   23.4.0.67
    - alfresco-enterprise-share: 23.4.0.46
    - acs-packaging:             23.4.0
    - RELEASE_VERSION:           23.4.0
    - DEVELOPMENT_VERSION:       25.1.0-A1-SNAPSHOT
2024-11-20 10:15:33 +00:00
alfresco-build fd4a4223df [release] 23.4.0-A17
Update upstream versions
    - alfresco-community-repo:   23.4.0.66
    - alfresco-enterprise-share: 23.4.0.45
    - acs-packaging:             23.4.0-A17
    - RELEASE_VERSION:           23.4.0-A17
    - DEVELOPMENT_VERSION:       23.4.0-A18-SNAPSHOT
2024-11-17 01:32:29 +00:00
Sara 2b051a5859 ACS-8937 Bump IE/SS to 2.0.13 (#1642) 2024-11-15 10:00:23 +00:00
alfresco-build 72f48d5a02 [release] 23.4.0-A16
Update upstream versions
    - alfresco-community-repo:   23.4.0.61
    - alfresco-enterprise-share: 23.4.0.42
    - acs-packaging:             23.4.0-A16
    - RELEASE_VERSION:           23.4.0-A16
    - DEVELOPMENT_VERSION:       23.4.0-A17-SNAPSHOT
2024-11-10 06:36:04 +00:00
alfresco-build a4e19d9a76 [maven-release-plugin][skip ci] prepare for next development iteration 2024-11-03 02:26:38 +00:00
alfresco-build 95b320258f [maven-release-plugin][skip ci] prepare release 23.4.0-A15 2024-11-03 02:26:37 +00:00
alfresco-build 7707fdf552 [release] 23.4.0-A15
Update upstream versions
    - alfresco-community-repo:   23.4.0.54
    - alfresco-enterprise-share: 23.4.0.33
    - acs-packaging:             23.4.0-A15
    - RELEASE_VERSION:           23.4.0-A15
    - DEVELOPMENT_VERSION:       23.4.0-A16-SNAPSHOT
2024-11-03 01:30:05 +00:00
alfresco-build c41960fb4e [maven-release-plugin][skip ci] prepare for next development iteration 2024-10-27 02:24:59 +00:00
alfresco-build ae77b1eb49 [maven-release-plugin][skip ci] prepare release 23.4.0-A14 2024-10-27 02:24:56 +00:00
alfresco-build badf8fdd13 [release] 23.4.0-A14
Update upstream versions
    - alfresco-community-repo:   23.4.0.52
    - alfresco-enterprise-share: 23.4.0.30
    - acs-packaging:             23.4.0-A14
    - RELEASE_VERSION:           23.4.0-A14
    - DEVELOPMENT_VERSION:       23.4.0-A15-SNAPSHOT
2024-10-27 01:26:17 +00:00
alfresco-build 5eb021421c [release] 23.4.0-A13
Update upstream versions
    - alfresco-community-repo:   23.4.0.49
    - alfresco-enterprise-share: 23.4.0.28
    - acs-packaging:             23.4.0-A13
    - RELEASE_VERSION:           23.4.0-A13
    - DEVELOPMENT_VERSION:       23.4.0-A14-SNAPSHOT
2024-10-20 01:33:16 +00:00
alfresco-build 88ff9f0411 [maven-release-plugin][skip ci] prepare for next development iteration 2024-10-16 07:57:00 +00:00
alfresco-build 224a922ecb [maven-release-plugin][skip ci] prepare release 23.4.0-A12 2024-10-16 07:56:57 +00:00
alfresco-build 34ba3a9845 [release] 23.4.0-A12
Update upstream versions
    - alfresco-community-repo:   23.4.0.47
    - alfresco-enterprise-share: 23.4.0.25
    - acs-packaging:             23.4.0-A12
    - RELEASE_VERSION:           23.4.0-A12
    - DEVELOPMENT_VERSION:       23.4.0-A13-SNAPSHOT
2024-10-16 06:57:16 +00:00
alfresco-build db207e34f8 [maven-release-plugin][skip ci] prepare for next development iteration 2024-10-08 07:15:46 +00:00
alfresco-build 723928d02a [maven-release-plugin][skip ci] prepare release 23.4.0-A11 2024-10-08 07:15:42 +00:00
alfresco-build b987a3da83 [release] 23.4.0-A11
Update upstream versions
    - alfresco-community-repo:   23.4.0.41
    - alfresco-enterprise-share: 23.4.0.20
    - acs-packaging:             23.4.0-A11
    - RELEASE_VERSION:           23.4.0-A11
    - DEVELOPMENT_VERSION:       23.4.0-A12-SNAPSHOT
2024-10-08 06:18:49 +00:00
alfresco-build 54d6c589cb [maven-release-plugin][skip ci] prepare for next development iteration 2024-10-06 02:23:11 +00:00
alfresco-build 60af28d6f7 [maven-release-plugin][skip ci] prepare release 23.4.0-A10 2024-10-06 02:23:09 +00:00
alfresco-build df073bb29e [release] 23.4.0-A10
Update upstream versions
    - alfresco-community-repo:   23.4.0.41
    - alfresco-enterprise-share: 23.4.0.18
    - acs-packaging:             23.4.0-A10
    - RELEASE_VERSION:           23.4.0-A10
    - DEVELOPMENT_VERSION:       23.4.0-A11-SNAPSHOT
2024-10-06 01:23:40 +00:00
alfresco-build 381cc7fef5 [maven-release-plugin][skip ci] prepare for next development iteration 2024-09-30 10:41:30 +00:00
alfresco-build ae0f441388 [maven-release-plugin][skip ci] prepare release 23.4.0-A9 2024-09-30 10:41:28 +00:00
alfresco-build db773ab6dc [release] 23.4.0-A9
Update upstream versions
    - alfresco-community-repo:   23.4.0.27
    - alfresco-enterprise-share: 23.4.0.15
    - acs-packaging:             23.4.0-A9
    - RELEASE_VERSION:           23.4.0-A9
    - DEVELOPMENT_VERSION:       23.4.0-A10-SNAPSHOT
2024-09-30 09:42:28 +00:00
alfresco-build 915f05565d [maven-release-plugin][skip ci] prepare for next development iteration 2024-09-30 09:17:45 +00:00
alfresco-build eb0c971daf [maven-release-plugin][skip ci] prepare release 23.4.0-M1 2024-09-30 09:17:42 +00:00
alfresco-build 2346357bc1 [release] 23.4.0-M1
Update upstream versions
    - alfresco-community-repo:   23.4.0.27
    - alfresco-enterprise-share: 23.4.0.15
    - acs-packaging:             23.4.0-M1
    - RELEASE_VERSION:           23.4.0-M1
    - DEVELOPMENT_VERSION:       23.4.0-A9-SNAPSHOT
2024-09-30 08:19:24 +00:00
alfresco-build cd5fdf55e4 [maven-release-plugin][skip ci] prepare for next development iteration 2024-09-29 02:30:44 +00:00
alfresco-build a0dc27fafd [maven-release-plugin][skip ci] prepare release 23.4.0-A8 2024-09-29 02:30:43 +00:00
alfresco-build 527c24d597 [release] 23.4.0-A8
Update upstream versions
    - alfresco-community-repo:   23.4.0.27
    - alfresco-enterprise-share: 23.4.0.15
    - acs-packaging:             23.4.0-A8
    - RELEASE_VERSION:           23.4.0-A8
    - DEVELOPMENT_VERSION:       23.4.0-A9-SNAPSHOT
2024-09-29 01:35:42 +00:00
alfresco-build b8ad8bf266 [maven-release-plugin][skip ci] prepare for next development iteration 2024-09-23 18:21:30 +00:00
alfresco-build 847a6b5a27 [maven-release-plugin][skip ci] prepare release 23.4.0-A7 2024-09-23 18:21:29 +00:00
alfresco-build e728eecc33 [release] 23.4.0-A7
Update upstream versions
    - alfresco-community-repo:   23.4.0.24
    - alfresco-enterprise-share: 23.4.0.13
    - acs-packaging:             23.4.0-A7
    - RELEASE_VERSION:           23.4.0-A7
    - DEVELOPMENT_VERSION:       23.4.0-A8-SNAPSHOT
2024-09-23 17:26:21 +00:00
alfresco-build 326de0cddf [maven-release-plugin][skip ci] prepare for next development iteration 2024-09-22 02:29:26 +00:00
alfresco-build 6dc44e028f [maven-release-plugin][skip ci] prepare release 23.4.0-A6 2024-09-22 02:29:24 +00:00
alfresco-build ec185bfefa [release] 23.4.0-A6
Update upstream versions
    - alfresco-community-repo:   23.4.0.24
    - alfresco-enterprise-share: 23.4.0.12
    - acs-packaging:             23.4.0-A6
    - RELEASE_VERSION:           23.4.0-A6
    - DEVELOPMENT_VERSION:       23.4.0-A7-SNAPSHOT
2024-09-22 01:30:44 +00:00
Aleksandra Onych 912657dcf4 [ACS-8670] bump actions to latest version (#1615) 2024-09-20 10:41:27 +02:00
alfresco-build 7e0c38d097 [release] 23.4.0-A5
Update upstream versions
    - alfresco-community-repo:   23.4.0.10
    - alfresco-enterprise-share: 23.4.0.9
    - acs-packaging:             23.4.0-A5
    - RELEASE_VERSION:           23.4.0-A5
    - DEVELOPMENT_VERSION:       23.4.0-A6-SNAPSHOT
2024-09-15 01:34:58 +00:00
alfresco-build 4dbe6b8c98 [maven-release-plugin][skip ci] prepare for next development iteration 2024-09-08 02:56:46 +00:00
alfresco-build 8df9f7bdf5 [maven-release-plugin][skip ci] prepare release 23.4.0-A4 2024-09-08 02:56:45 +00:00
alfresco-build 97cca8240c [release] 23.4.0-A4
Update upstream versions
    - alfresco-community-repo:   23.4.0.6
    - alfresco-enterprise-share: 23.4.0.6
    - acs-packaging:             23.4.0-A4
    - RELEASE_VERSION:           23.4.0-A4
    - DEVELOPMENT_VERSION:       23.4.0-A5-SNAPSHOT
2024-09-08 01:27:11 +00:00
alfresco-build 6aa8bcc853 [maven-release-plugin][skip ci] prepare for next development iteration 2024-09-01 02:38:11 +00:00
alfresco-build 7fcf8da378 [maven-release-plugin][skip ci] prepare release 23.4.0-A3 2024-09-01 02:38:09 +00:00
alfresco-build 4feef848b8 [release] 23.4.0-A3
Update upstream versions
    - alfresco-community-repo:   23.4.0.4
    - alfresco-enterprise-share: 23.4.0.2
    - acs-packaging:             23.4.0-A3
    - RELEASE_VERSION:           23.4.0-A3
    - DEVELOPMENT_VERSION:       23.4.0-A4-SNAPSHOT
2024-09-01 01:42:05 +00:00
alfresco-build 5321cc0ca6 [maven-release-plugin][skip ci] prepare for next development iteration 2024-08-28 20:10:48 +00:00
alfresco-build a7685e6c86 [maven-release-plugin][skip ci] prepare release 23.4.0-A2 2024-08-28 20:10:47 +00:00
alfresco-build a75c2f286f [release] 23.4.0-A2
Update upstream versions
    - alfresco-community-repo:   23.4.0.3
    - alfresco-enterprise-share: 23.3.0.86
    - acs-packaging:             23.4.0-A2
    - RELEASE_VERSION:           23.4.0-A2
    - DEVELOPMENT_VERSION:       23.4.0-A3-SNAPSHOT
2024-08-28 19:09:22 +00:00
mpichura 05349fd5f4 Updating master branch to 23.4.0 after 23.3.0 ACS release [skip ci] 2024-08-26 08:56:16 +02:00
alfresco-build bbfc00c39c [maven-release-plugin][skip ci] prepare for next development iteration 2024-08-25 02:27:24 +00:00
alfresco-build e2e1e8e7c1 [maven-release-plugin][skip ci] prepare release 23.4.0-A1 2024-08-25 02:27:23 +00:00
alfresco-build 65bd40e77e [release] 23.4.0-A1
Update upstream versions
    - alfresco-community-repo:   23.3.0.99
    - alfresco-enterprise-share: 23.3.0.86
    - acs-packaging:             23.4.0-A1
    - RELEASE_VERSION:           23.4.0-A1
    - DEVELOPMENT_VERSION:       23.4.0-A2-SNAPSHOT
2024-08-25 01:29:34 +00:00
alfresco-build 52d7524868 [maven-release-plugin][skip ci] prepare for next development iteration 2024-08-23 12:59:05 +00:00
alfresco-build 81023c8e99 [maven-release-plugin][skip ci] prepare release 23.3.0 2024-08-23 12:59:04 +00:00
alfresco-build 697f2820c1 [release][publish] 23.3.0
Update upstream versions
    - alfresco-community-repo:   23.3.0.98
    - alfresco-enterprise-share: 23.3.0.85
    - acs-packaging:             23.3.0
    - RELEASE_VERSION:           23.3.0
    - DEVELOPMENT_VERSION:       23.4.0-A1-SNAPSHOT
2024-08-23 12:01:14 +00:00
alfresco-build 2a555e44ec [maven-release-plugin][skip ci] prepare for next development iteration 2024-08-22 08:48:24 +00:00
alfresco-build 6d8f528ee4 [maven-release-plugin][skip ci] prepare release 23.3.0-A38 2024-08-22 08:48:22 +00:00
alfresco-build 32dbf00695 [release] 23.3.0-A38
Update upstream versions
    - alfresco-community-repo:   23.3.0.96
    - alfresco-enterprise-share: 23.3.0.83
    - acs-packaging:             23.3.0-A38
    - RELEASE_VERSION:           23.3.0-A38
    - DEVELOPMENT_VERSION:       23.3.0-A39-SNAPSHOT
2024-08-22 07:51:22 +00:00
alfresco-build e9d741b411 [release] 23.3.0-A37
Update upstream versions
    - alfresco-community-repo:   23.3.0.96
    - alfresco-enterprise-share: 23.3.0.83
    - acs-packaging:             23.3.0-A37
    - RELEASE_VERSION:           23.3.0-A37
    - DEVELOPMENT_VERSION:       23.3.0-A38-SNAPSHOT
2024-08-20 11:50:47 +00:00
alfresco-build 088c159ba4 [maven-release-plugin][skip ci] prepare for next development iteration 2024-08-18 02:26:11 +00:00
alfresco-build cb185f90d1 [maven-release-plugin][skip ci] prepare release 23.3.0-A36 2024-08-18 02:26:10 +00:00
alfresco-build f36db37acd [release] 23.3.0-A36
Update upstream versions
    - alfresco-community-repo:   23.3.0.95
    - alfresco-enterprise-share: 23.3.0.82
    - acs-packaging:             23.3.0-A36
    - RELEASE_VERSION:           23.3.0-A36
    - DEVELOPMENT_VERSION:       23.3.0-A37-SNAPSHOT
2024-08-18 01:28:10 +00:00
alfresco-build 75d5d39208 [maven-release-plugin][skip ci] prepare for next development iteration 2024-08-14 15:08:18 +00:00
alfresco-build d0c6c8b8b4 [maven-release-plugin][skip ci] prepare release 23.3.0-A35 2024-08-14 15:08:16 +00:00
alfresco-build bd0cac80ac [release] 23.3.0-A35
Update upstream versions
    - alfresco-community-repo:   23.3.0.94
    - alfresco-enterprise-share: 23.3.0.81
    - acs-packaging:             23.3.0-A35
    - RELEASE_VERSION:           23.3.0-A35
    - DEVELOPMENT_VERSION:       23.3.0-A36-SNAPSHOT
2024-08-14 14:09:04 +00:00
alfresco-build 4031b67417 [maven-release-plugin][skip ci] prepare for next development iteration 2024-08-14 08:24:18 +00:00
alfresco-build f2aa80352b [maven-release-plugin][skip ci] prepare release 23.3.0-A34 2024-08-14 08:24:17 +00:00
alfresco-build 26624ece0b [release] 23.3.0-A34
Update upstream versions
    - alfresco-community-repo:   23.3.0.94
    - alfresco-enterprise-share: 23.3.0.81
    - acs-packaging:             23.3.0-A34
    - RELEASE_VERSION:           23.3.0-A34
    - DEVELOPMENT_VERSION:       23.3.0-A35-SNAPSHOT
2024-08-14 07:26:39 +00:00
alfresco-build 86ac11e74e [maven-release-plugin][skip ci] prepare for next development iteration 2024-08-11 20:14:48 +00:00
alfresco-build 62abf6c0d3 [maven-release-plugin][skip ci] prepare release 23.3.0-A33 2024-08-11 20:14:47 +00:00
alfresco-build 96afa81b30 [release] 23.3.0-A33
Update upstream versions
    - alfresco-community-repo:   23.3.0.93
    - alfresco-enterprise-share: 23.3.0.80
    - acs-packaging:             23.3.0-A33
    - RELEASE_VERSION:           23.3.0-A33
    - DEVELOPMENT_VERSION:       23.3.0-A34-SNAPSHOT
2024-08-11 19:19:32 +00:00
alfresco-build 726ec9715b [release] 23.3.0-A32
Update upstream versions
    - alfresco-community-repo:   23.3.0.88
    - alfresco-enterprise-share: 23.3.0.79
    - acs-packaging:             23.3.0-A32
    - RELEASE_VERSION:           23.3.0-A32
    - DEVELOPMENT_VERSION:       23.3.0-A33-SNAPSHOT
2024-08-04 01:34:46 +00:00
alfresco-build 3a6eabb2b0 [maven-release-plugin][skip ci] prepare for next development iteration 2024-07-31 15:57:40 +00:00
alfresco-build b9175cda67 [maven-release-plugin][skip ci] prepare release 23.3.0-A31 2024-07-31 15:57:38 +00:00
alfresco-build d8e74c9a66 [release] 23.3.0-A31
Update upstream versions
    - alfresco-community-repo:   23.3.0.86
    - alfresco-enterprise-share: 23.3.0.78
    - acs-packaging:             23.3.0-A31
    - RELEASE_VERSION:           23.3.0-A31
    - DEVELOPMENT_VERSION:       23.3.0-A32-SNAPSHOT
2024-07-31 14:57:07 +00:00
alfresco-build 0d2508cfd8 [maven-release-plugin][skip ci] prepare for next development iteration 2024-07-30 15:48:05 +00:00
alfresco-build 0602443410 [maven-release-plugin][skip ci] prepare release 23.3.0-A30 2024-07-30 15:48:03 +00:00
alfresco-build bbf45555f9 [release] 23.3.0-A30
Update upstream versions
    - alfresco-community-repo:   23.3.0.85
    - alfresco-enterprise-share: 23.3.0.77
    - acs-packaging:             23.3.0-A30
    - RELEASE_VERSION:           23.3.0-A30
    - DEVELOPMENT_VERSION:       23.3.0-A31-SNAPSHOT
2024-07-30 14:50:40 +00:00
alfresco-build 1a4161bc4e [maven-release-plugin][skip ci] prepare for next development iteration 2024-07-28 02:17:57 +00:00
alfresco-build 0a8349beac [maven-release-plugin][skip ci] prepare release 23.3.0-A29 2024-07-28 02:17:55 +00:00
alfresco-build 600292cab8 [release] 23.3.0-A29
Update upstream versions
    - alfresco-community-repo:   23.3.0.85
    - alfresco-enterprise-share: 23.3.0.77
    - acs-packaging:             23.3.0-A29
    - RELEASE_VERSION:           23.3.0-A29
    - DEVELOPMENT_VERSION:       23.3.0-A30-SNAPSHOT
2024-07-28 01:18:05 +00:00
alfresco-build bc6e6e6fe4 [maven-release-plugin][skip ci] prepare for next development iteration 2024-07-14 02:22:28 +00:00
alfresco-build c36870e49c [maven-release-plugin][skip ci] prepare release 23.3.0-A28 2024-07-14 02:22:27 +00:00
alfresco-build 3a381a3425 [release] 23.3.0-A28
Update upstream versions
    - alfresco-community-repo:   23.3.0.73
    - alfresco-enterprise-share: 23.3.0.75
    - acs-packaging:             23.3.0-A28
    - RELEASE_VERSION:           23.3.0-A28
    - DEVELOPMENT_VERSION:       23.3.0-A29-SNAPSHOT
2024-07-14 01:25:42 +00:00
dependabot[bot] db50d96160 Bump org.codehaus.groovy:groovy from 3.0.19 to 3.0.22 (#1581)
Bumps [org.codehaus.groovy:groovy](https://github.com/apache/groovy) from 3.0.19 to 3.0.22.
- [Commits](https://github.com/apache/groovy/commits)

---
updated-dependencies:
- dependency-name: org.codehaus.groovy:groovy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-11 17:16:19 +02:00
dependabot[bot] ce2d1d0a3b Bump org.assertj:assertj-core from 3.24.2 to 3.26.3 (#1580)
Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.24.2 to 3.26.3.
- [Release notes](https://github.com/assertj/assertj/releases)
- [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.24.2...assertj-build-3.26.3)

---
updated-dependencies:
- dependency-name: org.assertj:assertj-core
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-11 16:58:36 +02:00
alfresco-build affa2b3fd1 [maven-release-plugin][skip ci] prepare for next development iteration 2024-07-08 13:05:41 +00:00
alfresco-build c1784b388f [maven-release-plugin][skip ci] prepare release 23.3.0-M1 2024-07-08 13:05:38 +00:00
alfresco-build 0e4b6df7af [release] 23.3.0-M1
Update upstream versions
    - alfresco-community-repo:   23.3.0.66
    - alfresco-enterprise-share: 23.3.0.74
    - acs-packaging:             23.3.0-M1
    - RELEASE_VERSION:           23.3.0-M1
    - DEVELOPMENT_VERSION:       23.3.0-A28-SNAPSHOT
2024-07-08 11:16:09 +00:00
alfresco-build 3f0a7757eb [maven-release-plugin][skip ci] prepare for next development iteration 2024-07-08 10:11:31 +00:00
alfresco-build a4d0e78887 [maven-release-plugin][skip ci] prepare release 23.3.0-A27 2024-07-08 10:11:30 +00:00
alfresco-build 898e4e2ad7 [release] 23.3.0-A27
Update upstream versions
    - alfresco-community-repo:   23.3.0.66
    - alfresco-enterprise-share: 23.3.0.74
    - acs-packaging:             23.3.0-A27
    - RELEASE_VERSION:           23.3.0-A27
    - DEVELOPMENT_VERSION:       23.3.0-A28-SNAPSHOT
2024-07-08 09:12:14 +00:00
Sara 0a6521de02 Bump SS/IE to 2.0.11 (#1568) 2024-07-04 16:44:58 +01:00
alfresco-build 05bdb689f2 [maven-release-plugin][skip ci] prepare for next development iteration 2024-07-01 12:01:32 +00:00
alfresco-build 6cc270264b [maven-release-plugin][skip ci] prepare release 23.3.0-A26 2024-07-01 12:01:29 +00:00
alfresco-build 57b7fe7d65 [release] 23.3.0-A26
Update upstream versions
    - alfresco-community-repo:   23.3.0.62
    - alfresco-enterprise-share: 23.3.0.73
    - acs-packaging:             23.3.0-A26
    - RELEASE_VERSION:           23.3.0-A26
    - DEVELOPMENT_VERSION:       23.3.0-A27-SNAPSHOT
2024-07-01 11:05:30 +00:00
alfresco-build fd4f1f24b0 [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-30 02:31:20 +00:00
alfresco-build bac86e7ca1 [maven-release-plugin][skip ci] prepare release 23.3.0-A25 2024-06-30 02:31:18 +00:00
alfresco-build 8c94e339dd [release] 23.3.0-A25
Update upstream versions
    - alfresco-community-repo:   23.3.0.62
    - alfresco-enterprise-share: 23.3.0.72
    - acs-packaging:             23.3.0-A25
    - RELEASE_VERSION:           23.3.0-A25
    - DEVELOPMENT_VERSION:       23.3.0-A26-SNAPSHOT
2024-06-30 01:33:53 +00:00
alfresco-build 07a5f7f88f [release] 23.3.0-A24
Update upstream versions
    - alfresco-community-repo:   23.3.0.60
    - alfresco-enterprise-share: 23.3.0.69
    - acs-packaging:             23.3.0-A24
    - RELEASE_VERSION:           23.3.0-A24
    - DEVELOPMENT_VERSION:       23.3.0-A25-SNAPSHOT
2024-06-27 05:54:46 +00:00
alfresco-build 544165e172 [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-25 13:59:40 +00:00
alfresco-build ee13662274 [maven-release-plugin][skip ci] prepare release 23.3.0-A23 2024-06-25 13:59:38 +00:00
alfresco-build db8676b73e [release] 23.3.0-A23
Update upstream versions
    - alfresco-community-repo:   23.3.0.57
    - alfresco-enterprise-share: 23.3.0.68
    - acs-packaging:             23.3.0-A23
    - RELEASE_VERSION:           23.3.0-A23
    - DEVELOPMENT_VERSION:       23.3.0-A24-SNAPSHOT
2024-06-25 13:02:02 +00:00
alfresco-build b363c0341c [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-23 02:21:05 +00:00
alfresco-build 8606ff3aa2 [maven-release-plugin][skip ci] prepare release 23.3.0-A22 2024-06-23 02:21:03 +00:00
alfresco-build c7625052ba [release] 23.3.0-A22
Update upstream versions
    - alfresco-community-repo:   23.3.0.57
    - alfresco-enterprise-share: 23.3.0.67
    - acs-packaging:             23.3.0-A22
    - RELEASE_VERSION:           23.3.0-A22
    - DEVELOPMENT_VERSION:       23.3.0-A23-SNAPSHOT
2024-06-23 01:22:06 +00:00
alfresco-build eb22a74f1f [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-17 18:50:30 +00:00
alfresco-build a858c7046e [maven-release-plugin][skip ci] prepare release 23.3.0-A21 2024-06-17 18:50:29 +00:00
alfresco-build 5944ef48c4 [release] 23.3.0-A21
Update upstream versions
    - alfresco-community-repo:   23.3.0.52
    - alfresco-enterprise-share: 23.3.0.61
    - acs-packaging:             23.3.0-A21
    - RELEASE_VERSION:           23.3.0-A21
    - DEVELOPMENT_VERSION:       23.3.0-A22-SNAPSHOT
2024-06-17 17:53:28 +00:00
Kacper Magdziarz f5aa0c93e6 Bump SS/IE to 2.0.11-A6 (#1562) 2024-06-17 14:56:49 +02:00
alfresco-build 90ffa23561 [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-16 02:17:57 +00:00
alfresco-build cca10c3bd4 [maven-release-plugin][skip ci] prepare release 23.3.0-A20 2024-06-16 02:17:55 +00:00
alfresco-build 3a8c21ae48 [release] 23.3.0-A20
Update upstream versions
    - alfresco-community-repo:   23.3.0.51
    - alfresco-enterprise-share: 23.3.0.57
    - acs-packaging:             23.3.0-A20
    - RELEASE_VERSION:           23.3.0-A20
    - DEVELOPMENT_VERSION:       23.3.0-A21-SNAPSHOT
2024-06-16 01:20:06 +00:00
alfresco-build dfdfbed093 [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-09 02:20:36 +00:00
alfresco-build b302eb9714 [maven-release-plugin][skip ci] prepare release 23.3.0-A19 2024-06-09 02:20:34 +00:00
alfresco-build dd9cb21d5c [release] 23.3.0-A19
Update upstream versions
    - alfresco-community-repo:   23.3.0.50
    - alfresco-enterprise-share: 23.3.0.52
    - acs-packaging:             23.3.0-A19
    - RELEASE_VERSION:           23.3.0-A19
    - DEVELOPMENT_VERSION:       23.3.0-A20-SNAPSHOT
2024-06-09 01:22:17 +00:00
alfresco-build 0dd61c2e0d [release] 23.3.0-A18
Update upstream versions
    - alfresco-community-repo:   23.3.0.46
    - alfresco-enterprise-share: 23.3.0.46
    - acs-packaging:             23.3.0-A18
    - RELEASE_VERSION:           23.3.0-A18
    - DEVELOPMENT_VERSION:       23.3.0-A19-SNAPSHOT
2024-06-05 06:09:12 +00:00
alfresco-build b5d253d6db [maven-release-plugin][skip ci] prepare for next development iteration 2024-06-02 02:35:54 +00:00
alfresco-build da4c4e24c5 [maven-release-plugin][skip ci] prepare release 23.3.0-A17 2024-06-02 02:35:51 +00:00
alfresco-build 32ae60eadf [release] 23.3.0-A17
Update upstream versions
    - alfresco-community-repo:   23.3.0.43
    - alfresco-enterprise-share: 23.3.0.42
    - acs-packaging:             23.3.0-A17
    - RELEASE_VERSION:           23.3.0-A17
    - DEVELOPMENT_VERSION:       23.3.0-A18-SNAPSHOT
2024-06-02 01:36:36 +00:00
alfresco-build 7d28684e55 [maven-release-plugin][skip ci] prepare for next development iteration 2024-05-26 02:17:19 +00:00
alfresco-build 7eaffbed80 [maven-release-plugin][skip ci] prepare release 23.3.0-A16 2024-05-26 02:17:17 +00:00
alfresco-build ee5830808e [release] 23.3.0-A16
Update upstream versions
    - alfresco-community-repo:   23.3.0.41
    - alfresco-enterprise-share: 23.3.0.37
    - acs-packaging:             23.3.0-A16
    - RELEASE_VERSION:           23.3.0-A16
    - DEVELOPMENT_VERSION:       23.3.0-A17-SNAPSHOT
2024-05-26 01:18:05 +00:00
alfresco-build 72ca0e34a1 [maven-release-plugin][skip ci] prepare for next development iteration 2024-05-19 02:28:51 +00:00
alfresco-build 3106d3e7be [maven-release-plugin][skip ci] prepare release 23.3.0-A15 2024-05-19 02:28:49 +00:00
alfresco-build c1a7f6ffb0 [release] 23.3.0-A15
Update upstream versions
    - alfresco-community-repo:   23.3.0.39
    - alfresco-enterprise-share: 23.3.0.35
    - acs-packaging:             23.3.0-A15
    - RELEASE_VERSION:           23.3.0-A15
    - DEVELOPMENT_VERSION:       23.3.0-A16-SNAPSHOT
2024-05-19 01:30:00 +00:00
alfresco-build d4f0006825 [maven-release-plugin][skip ci] prepare for next development iteration 2024-05-12 02:23:26 +00:00
alfresco-build 5af41219a3 [maven-release-plugin][skip ci] prepare release 23.3.0-A14 2024-05-12 02:23:24 +00:00
alfresco-build e5031aadb4 [release] 23.3.0-A14
Update upstream versions
    - alfresco-community-repo:   23.3.0.36
    - alfresco-enterprise-share: 23.3.0.33
    - acs-packaging:             23.3.0-A14
    - RELEASE_VERSION:           23.3.0-A14
    - DEVELOPMENT_VERSION:       23.3.0-A15-SNAPSHOT
2024-05-12 01:26:27 +00:00
alfresco-build e8b9b64afc [maven-release-plugin][skip ci] prepare for next development iteration 2024-05-05 02:23:05 +00:00
alfresco-build a9975152b7 [maven-release-plugin][skip ci] prepare release 23.3.0-A13 2024-05-05 02:23:03 +00:00
alfresco-build db5a85fc43 [release] 23.3.0-A13
Update upstream versions
    - alfresco-community-repo:   23.3.0.32
    - alfresco-enterprise-share: 23.3.0.28
    - acs-packaging:             23.3.0-A13
    - RELEASE_VERSION:           23.3.0-A13
    - DEVELOPMENT_VERSION:       23.3.0-A14-SNAPSHOT
2024-05-05 01:21:41 +00:00
alfresco-build fcedcd878b [maven-release-plugin][skip ci] prepare for next development iteration 2024-04-28 02:43:35 +00:00
alfresco-build a58cc3dc72 [maven-release-plugin][skip ci] prepare release 23.3.0-A12 2024-04-28 02:43:34 +00:00
alfresco-build e699b6cce5 [release] 23.3.0-A12
Update upstream versions
    - alfresco-community-repo:   23.3.0.28
    - alfresco-enterprise-share: 23.3.0.26
    - acs-packaging:             23.3.0-A12
    - RELEASE_VERSION:           23.3.0-A12
    - DEVELOPMENT_VERSION:       23.3.0-A13-SNAPSHOT
2024-04-28 01:43:09 +00:00
alfresco-build 3e82bb8707 [maven-release-plugin][skip ci] prepare for next development iteration 2024-04-21 02:48:11 +00:00
alfresco-build 511d5cafdb [maven-release-plugin][skip ci] prepare release 23.3.0-A11 2024-04-21 02:48:09 +00:00
alfresco-build 23b1718396 [release] 23.3.0-A11
Update upstream versions
    - alfresco-community-repo:   23.3.0.27
    - alfresco-enterprise-share: 23.3.0.25
    - acs-packaging:             23.3.0-A11
    - RELEASE_VERSION:           23.3.0-A11
    - DEVELOPMENT_VERSION:       23.3.0-A12-SNAPSHOT
2024-04-21 01:49:26 +00:00
Wojtek Świętoń 9a70909b6b ACS-6309 Fix reportportal dependencies 2024-04-16 10:46:54 +02:00
alfresco-build d652dc6d66 [maven-release-plugin][skip ci] prepare for next development iteration 2024-04-15 10:59:08 +00:00
alfresco-build c27b8dfeed [maven-release-plugin][skip ci] prepare release 23.3.0-A10 2024-04-15 10:59:06 +00:00
Domenico Sibilio 5532c6bc57 [skip tests][release] 23.3.0-A10 2024-04-15 12:33:59 +02:00
alfresco-build 10fbf62e00 [release] 23.3.0-A10
Update upstream versions
    - alfresco-community-repo:   23.3.0.25
    - alfresco-enterprise-share: 23.3.0.21
    - acs-packaging:             23.3.0-A10
    - RELEASE_VERSION:           23.3.0-A10
    - DEVELOPMENT_VERSION:       23.3.0-A11-SNAPSHOT
2024-04-15 09:18:29 +00:00
Domenico Sibilio 0a3e5c9770 ACS-7481 Bump IE/SS to 2.0.10 (#1553) 2024-04-12 14:57:56 +02:00
alfresco-build 15bdd187c8 [release] 23.3.0-A9
Update upstream versions
    - alfresco-community-repo:   23.3.0.21
    - alfresco-enterprise-share: 23.3.0.19
    - acs-packaging:             23.3.0-A9
    - RELEASE_VERSION:           23.3.0-A9
    - DEVELOPMENT_VERSION:       23.3.0-A10-SNAPSHOT
2024-04-07 01:22:50 +00:00
Domenico Sibilio cb8ec03d95 ACS-6961 Bump IE/SS to 2.0.10-A1 (#1550) 2024-04-05 14:10:49 +02:00
kcichonczyk 718f352838 [ACS-7470] migrate to docker compose v2 2024-04-04 11:20:59 +02:00
alfresco-build c7794747ee [maven-release-plugin][skip ci] prepare for next development iteration 2024-04-04 08:16:32 +00:00
alfresco-build b7ce513320 [maven-release-plugin][skip ci] prepare release 23.3.0-A8 2024-04-04 08:16:30 +00:00
alfresco-build da02d4d273 [release] 23.3.0-A8
Update upstream versions
    - alfresco-community-repo:   23.3.0.15
    - alfresco-enterprise-share: 23.3.0.18
    - acs-packaging:             23.3.0-A8
    - RELEASE_VERSION:           23.3.0-A8
    - DEVELOPMENT_VERSION:       23.3.0-A9-SNAPSHOT
2024-04-04 07:17:34 +00:00
alfresco-build 22a27773fd [release] 23.3.0-A7
Update upstream versions
    - alfresco-community-repo:   23.3.0.15
    - alfresco-enterprise-share: 23.3.0.17
    - acs-packaging:             23.3.0-A7
    - RELEASE_VERSION:           23.3.0-A7
    - DEVELOPMENT_VERSION:       23.3.0-A8-SNAPSHOT
2024-04-03 10:24:24 +00:00
alfresco-build c94e81c198 [maven-release-plugin][skip ci] prepare for next development iteration 2024-03-31 02:30:51 +00:00
alfresco-build 5aa54969af [maven-release-plugin][skip ci] prepare release 23.3.0-A6 2024-03-31 02:30:48 +00:00
alfresco-build 92b5a9b8e4 [release] 23.3.0-A6
Update upstream versions
    - alfresco-community-repo:   23.3.0.15
    - alfresco-enterprise-share: 23.3.0.16
    - acs-packaging:             23.3.0-A6
    - RELEASE_VERSION:           23.3.0-A6
    - DEVELOPMENT_VERSION:       23.3.0-A7-SNAPSHOT
2024-03-31 01:32:35 +00:00
alfresco-build c8ad52884a [maven-release-plugin][skip ci] prepare for next development iteration 2024-03-24 02:21:00 +00:00
alfresco-build 2dd77da097 [maven-release-plugin][skip ci] prepare release 23.3.0-A5 2024-03-24 02:20:59 +00:00
alfresco-build dc8f9bf4ea [release] 23.3.0-A5
Update upstream versions
    - alfresco-community-repo:   23.3.0.12
    - alfresco-enterprise-share: 23.3.0.15
    - acs-packaging:             23.3.0-A5
    - RELEASE_VERSION:           23.3.0-A5
    - DEVELOPMENT_VERSION:       23.3.0-A6-SNAPSHOT
2024-03-24 01:25:24 +00:00
alfresco-build 946ad7b9bd [maven-release-plugin][skip ci] prepare for next development iteration 2024-03-17 02:20:57 +00:00
alfresco-build 77100a81dc [maven-release-plugin][skip ci] prepare release 23.3.0-A4 2024-03-17 02:20:55 +00:00
alfresco-build 71cefc3d9a [release] 23.3.0-A4
Update upstream versions
    - alfresco-community-repo:   23.3.0.10
    - alfresco-enterprise-share: 23.3.0.12
    - acs-packaging:             23.3.0-A4
    - RELEASE_VERSION:           23.3.0-A4
    - DEVELOPMENT_VERSION:       23.3.0-A5-SNAPSHOT
2024-03-17 01:21:36 +00:00
alfresco-build 5b6b23f656 [release] 23.3.0-A3
Update upstream versions
    - alfresco-community-repo:   23.3.0.8
    - alfresco-enterprise-share: 23.3.0.11
    - acs-packaging:             23.3.0-A3
    - RELEASE_VERSION:           23.3.0-A3
    - DEVELOPMENT_VERSION:       23.3.0-A4-SNAPSHOT
2024-03-10 01:16:22 +00:00
alfresco-build 1d6460398a [maven-release-plugin][skip ci] prepare for next development iteration 2024-03-03 03:15:27 +00:00
alfresco-build 0fcef3cca8 [maven-release-plugin][skip ci] prepare release 23.3.0-A2 2024-03-03 03:15:25 +00:00
alfresco-build 4fb77ddba8 [release] 23.3.0-A2
Update upstream versions
    - alfresco-community-repo:   23.3.0.6
    - alfresco-enterprise-share: 23.3.0.9
    - acs-packaging:             23.3.0-A2
    - RELEASE_VERSION:           23.3.0-A2
    - DEVELOPMENT_VERSION:       23.3.0-A3-SNAPSHOT
2024-03-03 01:55:39 +00:00
alfresco-build e7226b42d4 [maven-release-plugin][skip ci] prepare for next development iteration 2024-02-25 02:14:59 +00:00
alfresco-build 8ec1a1a9b5 [maven-release-plugin][skip ci] prepare release 23.3.0-A1 2024-02-25 02:14:58 +00:00
alfresco-build 0e89c23b14 [release] 23.3.0-A1
Update upstream versions
    - alfresco-community-repo:   23.3.0.4
    - alfresco-enterprise-share: 23.3.0.4
    - acs-packaging:             23.3.0-A1
    - RELEASE_VERSION:           23.3.0-A1
    - DEVELOPMENT_VERSION:       23.3.0-A2-SNAPSHOT
2024-02-25 01:17:05 +00:00
mpichura 3213ce72c5 Updating master branch to 23.3.0 after 23.2.0 ACS release [skip ci] 2024-02-19 16:34:50 +01:00
alfresco-build ce62a721e4 [release] 23.2.0-A18
Update upstream versions
    - alfresco-community-repo:   23.2.0.50
    - alfresco-enterprise-share: 23.2.0.58
    - acs-packaging:             23.2.0-A18
    - RELEASE_VERSION:           23.2.0-A18
    - DEVELOPMENT_VERSION:       23.2.0-A19-SNAPSHOT
2024-02-18 01:37:16 +00:00
alfresco-build 5600bcc5ef [maven-release-plugin][skip ci] prepare for next development iteration 2024-02-14 00:19:50 +00:00
alfresco-build 7d7cc3cf0a [maven-release-plugin][skip ci] prepare release 23.2.0-A17 2024-02-14 00:19:48 +00:00
alfresco-build 806e7cbc3a [release] 23.2.0-A17
Update upstream versions
    - alfresco-community-repo:   23.2.0.45
    - alfresco-enterprise-share: 23.2.0.52
    - acs-packaging:             23.2.0-A17
    - RELEASE_VERSION:           23.2.0-A17
    - DEVELOPMENT_VERSION:       23.2.0-A18-SNAPSHOT
2024-02-13 23:18:11 +00:00
alfresco-build f766f25d97 [maven-release-plugin][skip ci] prepare for next development iteration 2024-02-11 02:19:17 +00:00
alfresco-build a4449af635 [maven-release-plugin][skip ci] prepare release 23.2.0-A16 2024-02-11 02:19:16 +00:00
alfresco-build 0791f53e89 [release] 23.2.0-A16
Update upstream versions
    - alfresco-community-repo:   23.2.0.45
    - alfresco-enterprise-share: 23.2.0.49
    - acs-packaging:             23.2.0-A16
    - RELEASE_VERSION:           23.2.0-A16
    - DEVELOPMENT_VERSION:       23.2.0-A17-SNAPSHOT
2024-02-11 01:18:39 +00:00
alfresco-build 56f76fba19 [maven-release-plugin][skip ci] prepare for next development iteration 2024-02-04 02:12:32 +00:00
alfresco-build f5ad17a9a0 [maven-release-plugin][skip ci] prepare release 23.2.0-A15 2024-02-04 02:12:31 +00:00
alfresco-build 9119a35939 [release] 23.2.0-A15
Update upstream versions
    - alfresco-community-repo:   23.2.0.41
    - alfresco-enterprise-share: 23.2.0.44
    - acs-packaging:             23.2.0-A15
    - RELEASE_VERSION:           23.2.0-A15
    - DEVELOPMENT_VERSION:       23.2.0-A16-SNAPSHOT
2024-02-04 01:16:00 +00:00
alfresco-build 8efa004e28 [maven-release-plugin][skip ci] prepare for next development iteration 2024-01-30 09:40:05 +00:00
alfresco-build 58751b98d8 [maven-release-plugin][skip ci] prepare release 23.2.0-A14 2024-01-30 09:40:03 +00:00
alfresco-build f0a77ca965 [release] 23.2.0-A14
Update upstream versions
    - alfresco-community-repo:   23.2.0.37
    - alfresco-enterprise-share: 23.2.0.39
    - acs-packaging:             23.2.0-A14
    - RELEASE_VERSION:           23.2.0-A14
    - DEVELOPMENT_VERSION:       23.2.0-A15-SNAPSHOT
2024-01-30 08:43:35 +00:00
alfresco-build 7da64a6ac2 [release] 23.2.0-A13
Update upstream versions
    - alfresco-community-repo:   23.2.0.37
    - alfresco-enterprise-share: 23.2.0.39
    - acs-packaging:             23.2.0-A13
    - RELEASE_VERSION:           23.2.0-A13
    - DEVELOPMENT_VERSION:       23.2.0-A14-SNAPSHOT
2024-01-28 01:21:00 +00:00
alfresco-build d7c09e862a [maven-release-plugin][skip ci] prepare for next development iteration 2024-01-21 02:30:09 +00:00
alfresco-build 354acdb9f5 [maven-release-plugin][skip ci] prepare release 23.2.0-A12 2024-01-21 02:30:07 +00:00
alfresco-build 8d4f482d75 [release] 23.2.0-A12
Update upstream versions
    - alfresco-community-repo:   23.2.0.34
    - alfresco-enterprise-share: 23.2.0.34
    - acs-packaging:             23.2.0-A12
    - RELEASE_VERSION:           23.2.0-A12
    - DEVELOPMENT_VERSION:       23.2.0-A13-SNAPSHOT
2024-01-21 01:27:39 +00:00
alfresco-build bec4dbca62 [maven-release-plugin][skip ci] prepare for next development iteration 2024-01-15 16:34:40 +00:00
alfresco-build 2becded5d3 [maven-release-plugin][skip ci] prepare release 23.2.0-M1 2024-01-15 16:34:38 +00:00
alfresco-build ed9e9f610e [release] 23.2.0-M1
Update upstream versions
    - alfresco-community-repo:   23.2.0.32
    - alfresco-enterprise-share: 23.2.0.32
    - acs-packaging:             23.2.0-M1
    - RELEASE_VERSION:           23.2.0-M1
    - DEVELOPMENT_VERSION:       23.2.0-A12-SNAPSHOT
2024-01-15 15:37:06 +00:00
alfresco-build 890a5bcd90 [maven-release-plugin][skip ci] prepare for next development iteration 2024-01-14 02:23:45 +00:00
alfresco-build 821c5fb519 [maven-release-plugin][skip ci] prepare release 23.2.0-A11 2024-01-14 02:23:44 +00:00
alfresco-build 8a36da0605 [release] 23.2.0-A11
Update upstream versions
    - alfresco-community-repo:   23.2.0.32
    - alfresco-enterprise-share: 23.2.0.32
    - acs-packaging:             23.2.0-A11
    - RELEASE_VERSION:           23.2.0-A11
    - DEVELOPMENT_VERSION:       23.2.0-A12-SNAPSHOT
2024-01-14 01:23:35 +00:00
alfresco-build f8c6a806eb [release] 23.2.0-A10
Update upstream versions
    - alfresco-community-repo:   23.2.0.31
    - alfresco-enterprise-share: 23.2.0.30
    - acs-packaging:             23.2.0-A10
    - RELEASE_VERSION:           23.2.0-A10
    - DEVELOPMENT_VERSION:       23.2.0-A11-SNAPSHOT
2024-01-07 01:17:23 +00:00
alfresco-build 5e99ab660b [maven-release-plugin][skip ci] prepare for next development iteration 2024-01-05 00:15:12 +00:00
alfresco-build a5c271c7b8 [maven-release-plugin][skip ci] prepare release 23.2.0-A9 2024-01-05 00:15:10 +00:00
alfresco-build f907d052b3 [release] 23.2.0-A9
Update upstream versions
    - alfresco-community-repo:   23.2.0.30
    - alfresco-enterprise-share: 23.2.0.28
    - acs-packaging:             23.2.0-A9
    - RELEASE_VERSION:           23.2.0-A9
    - DEVELOPMENT_VERSION:       23.2.0-A10-SNAPSHOT
2024-01-04 23:17:20 +00:00
alfresco-build 3ed5046777 [release] 23.2.0-A8
Update upstream versions
    - alfresco-community-repo:   23.2.0.26
    - alfresco-enterprise-share: 23.2.0.21
    - acs-packaging:             23.2.0-A8
    - RELEASE_VERSION:           23.2.0-A8
    - DEVELOPMENT_VERSION:       23.2.0-A9-SNAPSHOT
2023-12-31 01:51:13 +00:00
alfresco-build c1b243625b [maven-release-plugin][skip ci] prepare for next development iteration 2023-12-24 02:49:34 +00:00
alfresco-build fa56edd3f4 [maven-release-plugin][skip ci] prepare release 23.2.0-A7 2023-12-24 02:49:32 +00:00
alfresco-build 0de1ae65a9 [release] 23.2.0-A7
Update upstream versions
    - alfresco-community-repo:   23.2.0.25
    - alfresco-enterprise-share: 23.2.0.20
    - acs-packaging:             23.2.0-A7
    - RELEASE_VERSION:           23.2.0-A7
    - DEVELOPMENT_VERSION:       23.2.0-A8-SNAPSHOT
2023-12-24 01:46:05 +00:00
alfresco-build 4aec7abf93 [maven-release-plugin][skip ci] prepare for next development iteration 2023-12-17 02:10:14 +00:00
alfresco-build 05795e517c [maven-release-plugin][skip ci] prepare release 23.2.0-A6 2023-12-17 02:10:12 +00:00
alfresco-build d7d77f71dd [release] 23.2.0-A6
Update upstream versions
    - alfresco-community-repo:   23.2.0.22
    - alfresco-enterprise-share: 23.2.0.15
    - acs-packaging:             23.2.0-A6
    - RELEASE_VERSION:           23.2.0-A6
    - DEVELOPMENT_VERSION:       23.2.0-A7-SNAPSHOT
2023-12-17 01:14:05 +00:00
alfresco-build 5cc915f7f5 [maven-release-plugin][skip ci] prepare for next development iteration 2023-12-10 02:14:38 +00:00
alfresco-build c2f30f0bf9 [maven-release-plugin][skip ci] prepare release 23.2.0-A5 2023-12-10 02:14:37 +00:00
alfresco-build b65ae950fd [release] 23.2.0-A5
Update upstream versions
    - alfresco-community-repo:   23.2.0.18
    - alfresco-enterprise-share: 23.2.0.12
    - acs-packaging:             23.2.0-A5
    - RELEASE_VERSION:           23.2.0-A5
    - DEVELOPMENT_VERSION:       23.2.0-A6-SNAPSHOT
2023-12-10 01:17:32 +00:00
alfresco-build 9d39afd88c [maven-release-plugin][skip ci] prepare for next development iteration 2023-12-03 02:44:17 +00:00
alfresco-build b80a644f65 [maven-release-plugin][skip ci] prepare release 23.2.0-A4 2023-12-03 02:44:15 +00:00
alfresco-build 6d92cd441c [release] 23.2.0-A4
Update upstream versions
    - alfresco-community-repo:   23.2.0.17
    - alfresco-enterprise-share: 23.2.0.11
    - acs-packaging:             23.2.0-A4
    - RELEASE_VERSION:           23.2.0-A4
    - DEVELOPMENT_VERSION:       23.2.0-A5-SNAPSHOT
2023-12-03 01:22:11 +00:00
alfresco-build f5e366c6cf [maven-release-plugin][skip ci] prepare for next development iteration 2023-11-19 02:14:28 +00:00
alfresco-build adec7629f8 [maven-release-plugin][skip ci] prepare release 23.2.0-A3 2023-11-19 02:14:26 +00:00
alfresco-build 287dcffa0e [release] 23.2.0-A3
Update upstream versions
    - alfresco-community-repo:   23.2.0.13
    - alfresco-enterprise-share: 23.2.0.3
    - acs-packaging:             23.2.0-A3
    - RELEASE_VERSION:           23.2.0-A3
    - DEVELOPMENT_VERSION:       23.2.0-A4-SNAPSHOT
2023-11-19 01:17:20 +00:00
Domenico Sibilio d233044274 MNT-24063 Bump ActiveMQ to 5.18.3 (#1540) 2023-11-16 15:41:01 +01:00
alfresco-build 2b21baa935 [maven-release-plugin][skip ci] prepare for next development iteration 2023-11-12 02:39:11 +00:00
alfresco-build dcf3d00277 [maven-release-plugin][skip ci] prepare release 23.2.0-A2 2023-11-12 02:39:09 +00:00
alfresco-build e4613936f5 [release] 23.2.0-A2
Update upstream versions
    - alfresco-community-repo:   23.2.0.7
    - alfresco-enterprise-share: 23.2.0.2
    - acs-packaging:             23.2.0-A2
    - RELEASE_VERSION:           23.2.0-A2
    - DEVELOPMENT_VERSION:       23.2.0-A3-SNAPSHOT
2023-11-12 01:34:12 +00:00
alfresco-build 67aacbb0f6 [maven-release-plugin][skip ci] prepare for next development iteration 2023-11-05 02:39:52 +00:00
alfresco-build 7833d53b2a [maven-release-plugin][skip ci] prepare release 23.2.0-A1 2023-11-05 02:39:49 +00:00
alfresco-build 5549a53791 [release] 23.2.0-A1
Update upstream versions
    - alfresco-community-repo:   23.2.0.2
    - alfresco-enterprise-share: 23.2.0.1
    - acs-packaging:             23.2.0-A1
    - RELEASE_VERSION:           23.2.0-A1
    - DEVELOPMENT_VERSION:       23.2.0-A2-SNAPSHOT
2023-11-05 01:27:32 +00:00
mpichura 4f2bc499a7 Update Master branch to 23.2.0 [skip ci] 2023-10-31 19:37:16 +01:00
alfresco-build a5e44a583e [maven-release-plugin][skip ci] prepare for next development iteration 2023-10-31 16:57:25 +00:00
83 changed files with 9537 additions and 237 deletions
+11 -2
View File
@@ -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:
- "*"
+126 -27
View File
@@ -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 }}
@@ -28,11 +29,25 @@ env:
QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }}
TAS_ENVIRONMENT: ./tests/environment
TAS_SCRIPTS: ../alfresco-community-repo/packaging/tests/scripts
# Release version has to start with real version (7.4.0-....) for the docker image to build successfully.
RELEASE_VERSION: 23.1.0
DEVELOPMENT_VERSION: 23.2.0-A1-SNAPSHOT
# Release version has to start with real version (23.2.0-....) for the docker image to build successfully.
RELEASE_VERSION: 26.2.0-A.29
DEVELOPMENT_VERSION: 26.2.0-A.30-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 }}
+42
View File
@@ -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
+14
View File
@@ -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
+292
View File
@@ -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
View File
@@ -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
View File
@@ -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'
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
<properties>
+1 -1
View File
@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
<build>
+1 -1
View File
@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
<build>
+1 -1
View File
@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-dev-community-tomcat-env</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</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}
+2 -2
View File
@@ -25,7 +25,7 @@ services:
- 5432:5432
solr6:
image: alfresco/alfresco-search-services:2.0.3
image: alfresco/alfresco-search-services:2.0.21-A.2
mem_limit: 2g
environment:
#Solr needs to know how to register itself with Alfresco
@@ -47,7 +47,7 @@ services:
- 8983:8983 #Browser port
activemq:
image: alfresco/alfresco-activemq:5.17.4-jre17-rockylinux8
image: alfresco/alfresco-activemq:5.18.3-jre17-rockylinux8
mem_limit: 1g
ports:
- 8161:8161 # Web Console
+1 -1
View File
@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
<properties>
+1 -1
View File
@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
<dependencies>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
<dependencies>
+1 -1
View File
@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>content-services-community-docker</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
<properties>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
<properties>
+1 -1
View File
@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>share-community-docker</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
<properties>
+1 -1
View File
@@ -7,6 +7,6 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
</project>
+15 -6
View File
@@ -3,19 +3,19 @@
<artifactId>acs-community-packaging</artifactId>
<packaging>pom</packaging>
<name>Alfresco Content Services Community Packaging</name>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<relativePath>../alfresco-community-repo/pom.xml</relativePath>
<version>23.1.0.255</version>
<version>26.2.0.92</version>
</parent>
<properties>
<dependency.alfresco-community-repo.version>23.1.0.255</dependency.alfresco-community-repo.version>
<dependency.alfresco-community-share.version>23.1.0.269</dependency.alfresco-community-share.version>
<dependency.acs-packaging.version>23.1.0</dependency.acs-packaging.version> <!-- for Share distribution zip -->
<dependency.alfresco-community-repo.version>26.2.0.92</dependency.alfresco-community-repo.version>
<dependency.alfresco-community-share.version>26.2.0.65</dependency.alfresco-community-share.version>
<dependency.acs-packaging.version>26.2.0-A.29</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.1.0</tag>
<tag>26.2.0-A.29</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>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
<packaging>pom</packaging>
+10
View File
@@ -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
+1 -1
View File
@@ -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
+7
View File
@@ -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
+9 -3
View File
@@ -1,3 +1,9 @@
SOLR6_TAG=2.0.8.1
POSTGRES_TAG=15.4
ACTIVEMQ_TAG=5.17.4-jre17-rockylinux8
SOLR6_TAG=2.0.21-A.2
ACTIVEMQ_TAG=6.2.0-jre17-rockylinux8
ELASTICSEARCH_TAG=8.17.0
BATCH_INDEXING_TAG=5.7.0-A.5
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
View File
@@ -6,11 +6,13 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>acs-community-packaging</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</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>
+1 -1
View File
@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>content-repository-community-tests</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
<developers>
+2 -2
View File
@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>content-repository-community-tests</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</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>
@@ -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;
}
}
+135
View File
@@ -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.29</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>
@@ -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());
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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());
}
}
@@ -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();
}
}
@@ -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");
}
}
@@ -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");
}
}
}
@@ -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);
}
}
@@ -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"));
}
}
}
@@ -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;
}
}
@@ -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;
};
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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());
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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());
}
}
@@ -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:"));
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
}
}
@@ -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);
}
}
@@ -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

@@ -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>
@@ -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
+1 -1
View File
@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>content-repository-community-tests</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
<developers>
@@ -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);
}
}
+3 -3
View File
@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>content-repository-community-tests</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</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
+2 -2
View File
@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>content-repository-community-tests</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</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
+1 -1
View File
@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>content-repository-community-tests</artifactId>
<version>23.1.0</version>
<version>26.2.0-A.29</version>
</parent>
<developers>
+68
View File
@@ -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.29</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>
@@ -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_BASEURL", "http://transform-core-aio:8090/transform/config")
.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}