From d8325adbe37e344a5500543fb33a65eab54f580e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Enrique=20Colina=20Rodr=C3=ADguez?= Date: Fri, 18 Aug 2023 12:29:50 +0200 Subject: [PATCH] 22706 configure gha and it for cli (#25674) --- .github/actions/github-status/action.yml | 12 -- .github/actions/github-status/dist/index.js | 35 +----- .../github-status/src/github-status.ts | 35 +----- .github/actions/github-status/src/main.ts | 6 +- .github/workflows/cli-cicd-test.yml | 117 ++++++++++++++++++ cicd/resources/cli/cli-results-footer.html | 5 + cicd/resources/cli/cli-results-header.html | 17 +++ tools/dotcms-cli/api-data-model/pom.xml | 69 +++++++++++ .../client/YAMLFactoryServiceManagerImpl.java | 7 +- .../java/com/dotcms/ContainerResource.java | 78 ++++++++++++ .../test/java/com/dotcms/ContainerTest.java | 14 +++ ...Test.java => AssetAPIIntegrationTest.java} | 2 +- .../com/dotcms/api/AuthenticationAPITest.java | 1 - .../dotcms/api/AuthenticationContextTest.java | 2 +- ...ava => ContentTypeAPIIntegrationTest.java} | 9 +- ...est.java => FolderAPIIntegrationTest.java} | 2 +- ...ITest.java => SiteAPIIntegrationTest.java} | 2 +- ...=> DotCmsClientConfigIntegrationTest.java} | 2 +- .../dotcms/config/PropertiesConfigSource.java | 53 ++++++++ ...lipse.microprofile.config.spi.ConfigSource | 1 + .../src/test/resources/application.properties | 9 ++ .../src/test/resources/docker-compose.yaml | 72 +++++++++++ tools/dotcms-cli/cli/pom.xml | 85 ++++++++----- .../api/client/HybridServiceManagerImpl.java | 30 ++--- .../api/client/KeyTarPasswordStoreImpl.java | 48 +++++++ .../api/client/SecurePasswordStore.java | 29 +++++ .../java/com/dotcms/ContainerResource.java | 78 ++++++++++++ .../test/java/com/dotcms/ContainerTest.java | 14 +++ .../api/client/HybridServiceManagerTest.java | 49 ++++---- ...ockUnsupportedSecurePasswordStoreImpl.java | 29 +++++ ...t.java => PullServiceIntegrationTest.java} | 2 +- ...t.java => PushServiceIntegrationTest.java} | 2 +- .../cli/command/InstanceCommandTest.java | 12 +- .../dotcms/cli/command/StatusCommandTest.java | 15 +-- ...=> ContentTypeCommandIntegrationTest.java} | 2 +- ...ava => FilesLsCommandIntegrationTest.java} | 2 +- ...a => FilesPullCommandIntegrationTest.java} | 0 ...a => FilesTreeCommandIntegrationTest.java} | 2 +- ...va => LanguageCommandIntegrationTest.java} | 21 ++-- ...t.java => SiteCommandIntegrationTest.java} | 2 +- .../dotcms/config/PropertiesConfigSource.java | 53 ++++++++ ...lipse.microprofile.config.spi.ConfigSource | 1 + .../src/test/resources/application.properties | 9 ++ .../src/test/resources/docker-compose.yaml | 73 +++++++++++ tools/dotcms-cli/pom.xml | 5 + 45 files changed, 923 insertions(+), 190 deletions(-) create mode 100644 .github/workflows/cli-cicd-test.yml create mode 100644 cicd/resources/cli/cli-results-footer.html create mode 100644 cicd/resources/cli/cli-results-header.html create mode 100644 tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/ContainerResource.java create mode 100644 tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/ContainerTest.java rename tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/{AssetAPITest.java => AssetAPIIntegrationTest.java} (99%) rename tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/{ContentTypeAPITest.java => ContentTypeAPIIntegrationTest.java} (98%) rename tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/{FolderAPITest.java => FolderAPIIntegrationTest.java} (98%) rename tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/{SiteAPITest.java => SiteAPIIntegrationTest.java} (99%) rename tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/client/{DotCmsClientConfigTest.java => DotCmsClientConfigIntegrationTest.java} (90%) create mode 100644 tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/config/PropertiesConfigSource.java create mode 100644 tools/dotcms-cli/api-data-model/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource create mode 100644 tools/dotcms-cli/api-data-model/src/test/resources/application.properties create mode 100644 tools/dotcms-cli/api-data-model/src/test/resources/docker-compose.yaml create mode 100644 tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/KeyTarPasswordStoreImpl.java create mode 100644 tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/SecurePasswordStore.java create mode 100644 tools/dotcms-cli/cli/src/test/java/com/dotcms/ContainerResource.java create mode 100644 tools/dotcms-cli/cli/src/test/java/com/dotcms/ContainerTest.java create mode 100644 tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/MockUnsupportedSecurePasswordStoreImpl.java rename tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/{PullServiceTest.java => PullServiceIntegrationTest.java} (99%) rename tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/{PushServiceTest.java => PushServiceIntegrationTest.java} (99%) rename tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/{ContentTypeCommandTest.java => ContentTypeCommandIntegrationTest.java} (99%) rename tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/{FilesLsCommandTest.java => FilesLsCommandIntegrationTest.java} (99%) rename tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/{FilesPullCommandTest.java => FilesPullCommandIntegrationTest.java} (100%) rename tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/{FilesTreeCommandTest.java => FilesTreeCommandIntegrationTest.java} (99%) rename tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/language/{LanguageCommandTest.java => LanguageCommandIntegrationTest.java} (98%) rename tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/site/{SiteCommandTest.java => SiteCommandIntegrationTest.java} (99%) create mode 100644 tools/dotcms-cli/cli/src/test/java/com/dotcms/config/PropertiesConfigSource.java create mode 100644 tools/dotcms-cli/cli/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource create mode 100644 tools/dotcms-cli/cli/src/test/resources/application.properties create mode 100644 tools/dotcms-cli/cli/src/test/resources/docker-compose.yaml diff --git a/.github/actions/github-status/action.yml b/.github/actions/github-status/action.yml index c2f9e988bfea..6fdf15c3706a 100644 --- a/.github/actions/github-status/action.yml +++ b/.github/actions/github-status/action.yml @@ -5,19 +5,7 @@ author: 'victoralfaro-dotcms' inputs: test_type: description: 'Test Type' - type: choice - options: - - unit - - integration - - postman required: true - db_type: - description: 'Database type' - type: choice - options: - - postgres - - mysql - required: false test_results_status: description: 'Status of run tests' type: choice diff --git a/.github/actions/github-status/dist/index.js b/.github/actions/github-status/dist/index.js index c9a2ffaf4af9..87458558db61 100644 --- a/.github/actions/github-status/dist/index.js +++ b/.github/actions/github-status/dist/index.js @@ -49,10 +49,9 @@ const node_fetch_1 = __importDefault(__nccwpck_require__(4429)); * Sends the tests results statsus to Github using its API. * * @param testType test type - * @param dbType database type * @param testResultsStatus test results status (PASSED or FAILED) */ -const send = (testType, dbType, testResultsStatus) => __awaiter(void 0, void 0, void 0, function* () { +const send = (testType, testResultsStatus) => __awaiter(void 0, void 0, void 0, function* () { const pullRequest = core.getInput('pull_request'); if (!pullRequest) { core.warning(`This was not triggered from a pull request, so skipping sending status `); @@ -69,7 +68,7 @@ const send = (testType, dbType, testResultsStatus) => __awaiter(void 0, void 0, } const pr = (yield prResponse.json()); const testsReportUrl = core.getInput('tests_report_url'); - const status = createStatus(testType, dbType, testResultsStatus, testsReportUrl); + const status = createStatus(testType, testResultsStatus, testsReportUrl); const statusResponse = yield postStatus(pr._links.statuses.href, creds, status); if (!statusResponse.ok) { core.warning(`Could not send Github status for ${testType} tests`); @@ -77,35 +76,15 @@ const send = (testType, dbType, testResultsStatus) => __awaiter(void 0, void 0, } }); exports.send = send; -/** - * Resolves what label to use based on the test type. - * - * @param testType test type - * @param dbType database type - * @returns status label - */ -const resolveStastusLabel = (testType, dbType) => { - switch (testType) { - case 'unit': - return '[Unit tests results]'; - case 'integration': - return `[Integration tests results] - [${dbType}]`; - case 'postman': - return '[Postman tests results]'; - default: - return ''; - } -}; /** * Creates a status object based on the provided params. * * @param testType test type - * @param dbType database type * @param testResultsStatus test results status * @param testsReportUrl report url where tests results are located * @returns {@link GithubStatus} object to be used when reporting */ -const createStatus = (testType, dbType, testResultsStatus, testsReportUrl) => { +const createStatus = (testType, testResultsStatus, testsReportUrl) => { let statusLabel; let description; if (testResultsStatus === 'PASSED') { @@ -120,7 +99,7 @@ const createStatus = (testType, dbType, testResultsStatus, testsReportUrl) => { state: statusLabel, description, target_url: testsReportUrl, - context: `Github Actions - ${resolveStastusLabel(testType, dbType)}` + context: `Github Actions - [${testType.toUpperCase()} tests results]` }; }; /** @@ -209,11 +188,9 @@ const github = __importStar(__nccwpck_require__(6684)); */ const run = () => { const testType = core.getInput('test_type'); - const dbType = core.getInput('db_type'); const testResultsStatus = core.getInput('test_results_status'); - const dbLabel = !!dbType ? ` with ${dbType} database` : ''; - core.info(`Submitting ${testResultsStatus} status to Github for ${testType} tests${dbLabel}`); - github.send(testType, dbType, testResultsStatus); + core.info(`Submitting ${testResultsStatus} status to Github for ${testType} tests`); + github.send(testType, testResultsStatus); }; // Run main function run(); diff --git a/.github/actions/github-status/src/github-status.ts b/.github/actions/github-status/src/github-status.ts index 26144f6f1ddd..caa5d96ee344 100644 --- a/.github/actions/github-status/src/github-status.ts +++ b/.github/actions/github-status/src/github-status.ts @@ -20,10 +20,9 @@ interface LinksSupport { * Sends the tests results statsus to Github using its API. * * @param testType test type - * @param dbType database type * @param testResultsStatus test results status (PASSED or FAILED) */ -export const send = async (testType: string, dbType: string, testResultsStatus: string) => { +export const send = async (testType: string, testResultsStatus: string) => { const pullRequest = core.getInput('pull_request') if (!pullRequest) { core.warning(`This was not triggered from a pull request, so skipping sending status `) @@ -43,7 +42,7 @@ export const send = async (testType: string, dbType: string, testResultsStatus: const pr = (await prResponse.json()) as LinksSupport const testsReportUrl = core.getInput('tests_report_url') - const status = createStatus(testType, dbType, testResultsStatus, testsReportUrl) + const status = createStatus(testType, testResultsStatus, testsReportUrl) const statusResponse = await postStatus(pr._links.statuses.href, creds, status) if (!statusResponse.ok) { core.warning(`Could not send Github status for ${testType} tests`) @@ -51,41 +50,15 @@ export const send = async (testType: string, dbType: string, testResultsStatus: } } -/** - * Resolves what label to use based on the test type. - * - * @param testType test type - * @param dbType database type - * @returns status label - */ -const resolveStastusLabel = (testType: string, dbType: string): string => { - switch (testType) { - case 'unit': - return '[Unit tests results]' - case 'integration': - return `[Integration tests results] - [${dbType}]` - case 'postman': - return '[Postman tests results]' - default: - return '' - } -} - /** * Creates a status object based on the provided params. * * @param testType test type - * @param dbType database type * @param testResultsStatus test results status * @param testsReportUrl report url where tests results are located * @returns {@link GithubStatus} object to be used when reporting */ -const createStatus = ( - testType: string, - dbType: string, - testResultsStatus: string, - testsReportUrl: string -): GithubStatus => { +const createStatus = (testType: string, testResultsStatus: string, testsReportUrl: string): GithubStatus => { let statusLabel let description if (testResultsStatus === 'PASSED') { @@ -100,7 +73,7 @@ const createStatus = ( state: statusLabel, description, target_url: testsReportUrl, - context: `Github Actions - ${resolveStastusLabel(testType, dbType)}` + context: `Github Actions - [${testType.toUpperCase()} tests results]` } } diff --git a/.github/actions/github-status/src/main.ts b/.github/actions/github-status/src/main.ts index b6939af75ab6..76b0e1094859 100644 --- a/.github/actions/github-status/src/main.ts +++ b/.github/actions/github-status/src/main.ts @@ -6,12 +6,10 @@ import * as github from './github-status' */ const run = () => { const testType = core.getInput('test_type') - const dbType = core.getInput('db_type') const testResultsStatus = core.getInput('test_results_status') - const dbLabel = !!dbType ? ` with ${dbType} database` : '' - core.info(`Submitting ${testResultsStatus} status to Github for ${testType} tests${dbLabel}`) - github.send(testType, dbType, testResultsStatus) + core.info(`Submitting ${testResultsStatus} status to Github for ${testType} tests`) + github.send(testType, testResultsStatus) } // Run main function diff --git a/.github/workflows/cli-cicd-test.yml b/.github/workflows/cli-cicd-test.yml new file mode 100644 index 000000000000..4ab7b2e07ae8 --- /dev/null +++ b/.github/workflows/cli-cicd-test.yml @@ -0,0 +1,117 @@ +name: CLI tests +on: + pull_request: + paths: + - 'tools/dotcms-cli/**' + push: + branches: + - master + - release-* +jobs: + cli_tests: + name: CLI tests + runs-on: ubuntu-latest + outputs: + tests_results_status: ${{ steps.run-cli-tests.outputs.status }} + tests_results_report_url: ${{ steps.publish-test-results.outputs.tests_report_url }} + tests_results_log_url: ${{ steps.publish-test-results.outputs.test_logs_url }} + steps: + - id: checkout-core + name: Checkout core + uses: actions/checkout@v3 + - id: prepare-license + name: Prepare license + working-directory: ./tools/dotcms-cli/ + env: + DOTCMS_LICENSE_KEY: ${{ secrets.DOTCMS_LICENSE }} + run: | + DOTCMS_LICENSE_PATH=${GITHUB_WORKSPACE}/tools/dotcms-cli/license + mkdir -p ${DOTCMS_LICENSE_PATH} + echo "${DOTCMS_LICENSE_KEY}" > ${DOTCMS_LICENSE_PATH}/license.dat + echo "DOTCMS_LICENSE_FILE=${DOTCMS_LICENSE_PATH}/license.dat" >> "$GITHUB_ENV" + + - id: run-cli-tests + name: Run cli tests + working-directory: ./tools/dotcms-cli/ + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + BUILD_ID="${{ github.head_ref }}" + else + BUILD_ID=$(basename "${{ github.ref }}") + fi + + ./mvnw clean verify + + FAILURES=$(find target -name TEST-*.xml | xargs grep '> $GITHUB_OUTPUT + + ./mvnw surefire-report:report-only surefire-report:failsafe-report-only + + DOT_CLI_PATH=${{ github.workspace }}/tools/dotcms-cli + RESULTS_PATH=${DOT_CLI_PATH}/target/test-results + XML_REPORTS_PATH=${RESULTS_PATH}/xml-reports + HTML_REPORTS_PATH=${RESULTS_PATH}/html-reports + POM_FILE=${DOT_CLI_PATH}/pom.xml + CICD_RES_PATH=${{ github.workspace }}/cicd/resources + + INDEX_FILE=${HTML_REPORTS_PATH}/index.html + mkdir -p ${HTML_REPORTS_PATH} + touch ${INDEX_FILE} + cat ${CICD_RES_PATH}/cli/cli-results-header.html >> ${INDEX_FILE} + + MODULES=$(grep "" $POM_FILE | sed 's///' | sed 's/<\/module>//') + for module in $MODULES + do + mkdir -p ${XML_REPORTS_PATH}/${module} + cp -r ${module}/target/surefire-reports ${XML_REPORTS_PATH}/${module} + cp -r ${module}/target/failsafe-reports ${XML_REPORTS_PATH}/${module} + mkdir -p ${HTML_REPORTS_PATH}/${module} + cp -r ${module}/target/site/* ${HTML_REPORTS_PATH}/${module} + + for report in surefire failsafe + do + echo "${module}${report} report" >> ${INDEX_FILE} + done + done + + cat ${CICD_RES_PATH}/cli/cli-results-footer.html >> ${INDEX_FILE} + + echo "BUILD_ID=${BUILD_ID}" >> "$GITHUB_ENV" + echo "results_location=${XML_REPORTS_PATH}" >> "$GITHUB_OUTPUT" + echo "results_report_location=${HTML_REPORTS_PATH}" >> "$GITHUB_OUTPUT" + + tree ${RESULTS_PATH} + + - id: publish-test-results + name: Github publish tests results + uses: ./.github/actions/publish-test-results + with: + build_id: ${{ env.BUILD_ID }} + build_hash: ${{ env.BUILD_HASH }} + test_type: cli + pull_request: ${{ github.event.number }} # optional in case the tests are triggered by a PR + tests_results_status: ${{ steps.run-cli-tests.outputs.status }} # tests results status, optional + tests_results_location: ${{ steps.run-cli-tests.outputs.results_location }} # tests results location, optional, most of the time this made up of a bunch of XML files, optional + tests_results_report_location: ${{ steps.run-cli-tests.outputs.results_report_location }} # actual reports made from XML files, probably in HTML format + tests_results_repo: tools-test-results + cicd_github_token: ${{ secrets.CICD_GITHUB_TOKEN }} + include: RESULTS + + - id: github-status + name: Send Github Status + uses: ./.github/actions/github-status + with: + test_type: cli + test_results_status: ${{ steps.run-cli-tests.outputs.status }} + pull_request: ${{ github.event.number }} + github_user: ${{ env.GITHUB_USER }} + cicd_github_token: ${{ secrets.CICD_GITHUB_TOKEN }} + tests_report_url: ${{ steps.publish-test-results.outputs.tests_report_url }} + if: (success() || failure()) && steps.publish-test-results.outputs.tests_report_url != '' diff --git a/cicd/resources/cli/cli-results-footer.html b/cicd/resources/cli/cli-results-footer.html new file mode 100644 index 000000000000..403a90b78429 --- /dev/null +++ b/cicd/resources/cli/cli-results-footer.html @@ -0,0 +1,5 @@ + + + + + diff --git a/cicd/resources/cli/cli-results-header.html b/cicd/resources/cli/cli-results-header.html new file mode 100644 index 000000000000..28dfb130221f --- /dev/null +++ b/cicd/resources/cli/cli-results-header.html @@ -0,0 +1,17 @@ + + + + + CLI Reports + + + + + + + +
+

CLI Reports

+ + + diff --git a/tools/dotcms-cli/api-data-model/pom.xml b/tools/dotcms-cli/api-data-model/pom.xml index 0dff007b1491..ba35d8ded4c5 100644 --- a/tools/dotcms-cli/api-data-model/pom.xml +++ b/tools/dotcms-cli/api-data-model/pom.xml @@ -22,6 +22,10 @@ io.quarkus.platform2.9.1.Final3.0.0-M5 + 1.17.6 + false + false + true @@ -99,6 +103,42 @@ io.quarkus quarkus-devtools-registry-client + + + org.junit.jupiter + junit-jupiter + test + + + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + + org.testcontainers + testcontainers + ${testcontainers.version} + test + + + + org.testcontainers + postgresql + ${testcontainers.version} + test + + + + org.testcontainers + elasticsearch + ${testcontainers.version} + test + + + @@ -131,12 +171,41 @@ maven-surefire-plugin ${surefire-plugin.version} + ${test.failure.ignore} + ${skip.surefire.tests} org.jboss.logmanager.LogManager ${maven.home} + + org.apache.maven.plugins + maven-failsafe-plugin + ${surefire-plugin.version} + + ${test.failure.ignore} + ${skip.failsafe.tests} + + **/*IntegrationTest.java + + + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + diff --git a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/api/client/YAMLFactoryServiceManagerImpl.java b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/api/client/YAMLFactoryServiceManagerImpl.java index e9f245245e32..0f48a5e6fcd1 100644 --- a/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/api/client/YAMLFactoryServiceManagerImpl.java +++ b/tools/dotcms-cli/api-data-model/src/main/java/com/dotcms/api/client/YAMLFactoryServiceManagerImpl.java @@ -4,11 +4,7 @@ import com.dotcms.model.config.ServiceBean; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import javax.enterprise.context.ApplicationScoped; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -18,8 +14,9 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import javax.enterprise.context.ApplicationScoped; +import org.eclipse.microprofile.config.inject.ConfigProperty; -//@DefaultBean @ApplicationScoped public class YAMLFactoryServiceManagerImpl implements ServiceManager { diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/ContainerResource.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/ContainerResource.java new file mode 100644 index 000000000000..9afe255a6428 --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/ContainerResource.java @@ -0,0 +1,78 @@ +package com.dotcms; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import io.quarkus.test.junit.QuarkusTest; +import org.apache.http.HttpStatus; +import org.eclipse.microprofile.config.ConfigProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; + +import java.io.File; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +/** + * ContainerResource implements the QuarkusTestResourceLifecycleManager interface to manage starting and stopping the Docker containers for testing. + * The class has constants for the ports of the Postgres, Elasticsearch, and dotCMS services exposed by Docker Compose. + * The static initializer block reads test configuration from MicroProfile Config and creates a DockerComposeContainer. + * It configures exposed services, environment variables, and logging. + * The start() method starts the Docker Compose container and returns a map with the dotCMS URL for tests to use. + * The stop() method stops the Docker Compose container when tests are complete. + */ +@QuarkusTest +public class ContainerResource implements QuarkusTestResourceLifecycleManager { + + private static final int POSTGRES_SERVICE_PORT; + private static final int ELASTICSEARCH_SERVICE_PORT; + private static final int DOTCMS_SERVICE_PORT; + private static final Logger LOGGER = LoggerFactory.getLogger(ContainerResource.class); + + static DockerComposeContainer COMPOSE_CONTAINER; + + static { + + POSTGRES_SERVICE_PORT = Integer.parseInt(ConfigProvider.getConfig().getValue("testcontainers.postgres.service.port", String.class)); + ELASTICSEARCH_SERVICE_PORT = Integer.parseInt(ConfigProvider.getConfig().getValue("testcontainers.elasticsearch.service.port", String.class)); + DOTCMS_SERVICE_PORT = Integer.parseInt(ConfigProvider.getConfig().getValue("testcontainers.dotcms.service.port", String.class)); + + final boolean isLoggerEnabled = Boolean.parseBoolean(ConfigProvider.getConfig().getValue("testcontainers.logger.enabled", String.class)); + final boolean isLocalComposeEnabled = Boolean.parseBoolean(ConfigProvider.getConfig().getValue("testcontainers.docker.compose.local.enabled", String.class)); + final String dockerImage = ConfigProvider.getConfig().getValue("testcontainers.docker.image", String.class); + final String dockerComposeFile = ConfigProvider.getConfig().getValue("testcontainers.docker.compose.file", String.class); + final String dotcmsLicenseFile = ConfigProvider.getConfig().getValue("testcontainers.dotcms.license.file", String.class); + final long dockerComposeStartupTimeout = ConfigProvider.getConfig().getValue("testcontainers.docker.compose.startup.timeout", Long.class); + + DockerComposeContainer dockerComposeContainer = new DockerComposeContainer("dotcms-env", new File(dockerComposeFile)); + dockerComposeContainer.withExposedService("postgres", POSTGRES_SERVICE_PORT, Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(dockerComposeStartupTimeout))); + dockerComposeContainer.withExposedService("elasticsearch", ELASTICSEARCH_SERVICE_PORT, Wait.forHttp("/").forPort(ELASTICSEARCH_SERVICE_PORT).forStatusCode(HttpStatus.SC_OK)); + dockerComposeContainer.withExposedService("dotcms", DOTCMS_SERVICE_PORT, Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(dockerComposeStartupTimeout))); + dockerComposeContainer.withEnv("DOTCMS_IMAGE", dockerImage); + dockerComposeContainer.withEnv("DOTCMS_LICENSE_FILE", dotcmsLicenseFile); + + if (isLoggerEnabled) { + dockerComposeContainer.withLogConsumer("dotcms", new Slf4jLogConsumer(LOGGER)); + } + + dockerComposeContainer.withLocalCompose(isLocalComposeEnabled); // Needs to be false to run in macOS + COMPOSE_CONTAINER = dockerComposeContainer; + } + + @Override + public Map start() { + + COMPOSE_CONTAINER.start(); + final Map conf = new HashMap<>(); + conf.put("%test.dotcms.url", COMPOSE_CONTAINER.getServiceHost("dotcms", DOTCMS_SERVICE_PORT) + ":" + COMPOSE_CONTAINER.getServicePort("dotcms", DOTCMS_SERVICE_PORT)); + + return conf; + } + + @Override + public void stop() { + COMPOSE_CONTAINER.stop(); + } +} diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/ContainerTest.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/ContainerTest.java new file mode 100644 index 000000000000..6f524548470a --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/ContainerTest.java @@ -0,0 +1,14 @@ +package com.dotcms; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; + +import static io.restassured.RestAssured.given; + +@QuarkusTest +@QuarkusTestResource(ContainerResource.class) +public class ContainerTest { +} + diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AssetAPITest.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AssetAPIIntegrationTest.java similarity index 99% rename from tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AssetAPITest.java rename to tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AssetAPIIntegrationTest.java index 47bd1216b331..52e4d1b517f2 100644 --- a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AssetAPITest.java +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AssetAPIIntegrationTest.java @@ -22,7 +22,7 @@ import java.util.Map; @QuarkusTest -class AssetAPITest { +class AssetAPIIntegrationTest { @ConfigProperty(name = "com.dotcms.starter.site", defaultValue = "default") String siteName; diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AuthenticationAPITest.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AuthenticationAPITest.java index 033f4a8bf600..23867989e06d 100644 --- a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AuthenticationAPITest.java +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AuthenticationAPITest.java @@ -8,7 +8,6 @@ import java.util.Optional; import javax.inject.Inject; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AuthenticationContextTest.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AuthenticationContextTest.java index 2dce8f3abc41..f89c55b40e9c 100644 --- a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AuthenticationContextTest.java +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/AuthenticationContextTest.java @@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test; @QuarkusTest -public class AuthenticationContextTest { +class AuthenticationContextTest { @Inject AuthenticationContext authenticationContext; diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPITest.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPIIntegrationTest.java similarity index 98% rename from tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPITest.java rename to tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPIIntegrationTest.java index 0b67ba16ab82..dbba71a54643 100644 --- a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPITest.java +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/ContentTypeAPIIntegrationTest.java @@ -3,7 +3,12 @@ import com.dotcms.api.client.RestClientFactory; import com.dotcms.api.client.ServiceManager; import com.dotcms.api.provider.ClientObjectMapper; -import com.dotcms.contenttype.model.field.*; +import com.dotcms.contenttype.model.field.BinaryField; +import com.dotcms.contenttype.model.field.FieldLayoutRow; +import com.dotcms.contenttype.model.field.ImmutableBinaryField; +import com.dotcms.contenttype.model.field.ImmutableColumnField; +import com.dotcms.contenttype.model.field.ImmutableRowField; +import com.dotcms.contenttype.model.field.ImmutableTextField; import com.dotcms.contenttype.model.type.BaseContentType; import com.dotcms.contenttype.model.type.ContentType; import com.dotcms.contenttype.model.type.ImmutableSimpleContentType; @@ -33,7 +38,7 @@ import org.wildfly.common.Assert; @QuarkusTest -class ContentTypeAPITest { +class ContentTypeAPIIntegrationTest { @ConfigProperty(name = "com.dotcms.starter.site", defaultValue = "default") String siteName; diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/FolderAPITest.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/FolderAPIIntegrationTest.java similarity index 98% rename from tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/FolderAPITest.java rename to tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/FolderAPIIntegrationTest.java index 483ba7f18ec6..2933aab576e8 100644 --- a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/FolderAPITest.java +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/FolderAPIIntegrationTest.java @@ -17,7 +17,7 @@ import org.junit.jupiter.api.Test; @QuarkusTest -class FolderAPITest { +class FolderAPIIntegrationTest { @ConfigProperty(name = "com.dotcms.starter.site", defaultValue = "default") String siteName; diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/SiteAPITest.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/SiteAPIIntegrationTest.java similarity index 99% rename from tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/SiteAPITest.java rename to tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/SiteAPIIntegrationTest.java index a6fec4aefc03..2fd43168cf07 100644 --- a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/SiteAPITest.java +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/SiteAPIIntegrationTest.java @@ -18,7 +18,7 @@ import org.wildfly.common.Assert; @QuarkusTest -class SiteAPITest { +class SiteAPIIntegrationTest { @ConfigProperty(name = "com.dotcms.starter.site", defaultValue = "default") String siteName; diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/client/DotCmsClientConfigTest.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/client/DotCmsClientConfigIntegrationTest.java similarity index 90% rename from tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/client/DotCmsClientConfigTest.java rename to tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/client/DotCmsClientConfigIntegrationTest.java index c28f4220085e..fe74563d88c2 100644 --- a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/client/DotCmsClientConfigTest.java +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/api/client/DotCmsClientConfigIntegrationTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test; @QuarkusTest -class DotCmsClientConfigTest { +class DotCmsClientConfigIntegrationTest { @Inject DotCmsClientConfig config; diff --git a/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/config/PropertiesConfigSource.java b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/config/PropertiesConfigSource.java new file mode 100644 index 000000000000..9b05b9d58867 --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/test/java/com/dotcms/config/PropertiesConfigSource.java @@ -0,0 +1,53 @@ +package com.dotcms.config; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.Set; + +/** + * This class is used to load the properties from the application.properties file. + * It is needed to inject the properties into a static block. + */ +public class PropertiesConfigSource implements ConfigSource { + private Properties properties; + + public PropertiesConfigSource() { + loadPropertiesFromFile(); + } + + /** + * Load the properties from the application.properties file. + */ + private void loadPropertiesFromFile() { + properties = new Properties(); + try (InputStream inputStream = getClass().getResourceAsStream("/application.properties")) { + properties.load(inputStream); + } catch (IOException e) { + // Handle the exception or log the error + } + } + + @Override + public Set getPropertyNames() { + return properties.stringPropertyNames(); + } + + @Override + public String getValue(String propertyName) { + return properties.getProperty(propertyName); + } + + @Override + public String getName() { + return "DiskConfigSource"; + } + + @Override + public int getOrdinal() { + return 100; // The ordinal of the config source. Config sources with a lower ordinal are tried first. + } + +} diff --git a/tools/dotcms-cli/api-data-model/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/tools/dotcms-cli/api-data-model/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource new file mode 100644 index 000000000000..93e05c9c536f --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource @@ -0,0 +1 @@ +com.dotcms.config.PropertiesConfigSource \ No newline at end of file diff --git a/tools/dotcms-cli/api-data-model/src/test/resources/application.properties b/tools/dotcms-cli/api-data-model/src/test/resources/application.properties new file mode 100644 index 000000000000..53f9b5459c12 --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/test/resources/application.properties @@ -0,0 +1,9 @@ +testcontainers.logger.enabled=true +testcontainers.docker.compose.local.enabled=false +testcontainers.docker.image=${DOTCMS_IMAGE:dotcms/dotcms:master_latest_SNAPSHOT} +testcontainers.docker.compose.file=src/test/resources/docker-compose.yaml +testcontainers.docker.compose.startup.timeout=120 +testcontainers.dotcms.license.file=${DOTCMS_LICENSE_FILE} +testcontainers.dotcms.service.port=8080 +testcontainers.elasticsearch.service.port=9200 +testcontainers.postgres.service.port=5432 \ No newline at end of file diff --git a/tools/dotcms-cli/api-data-model/src/test/resources/docker-compose.yaml b/tools/dotcms-cli/api-data-model/src/test/resources/docker-compose.yaml new file mode 100644 index 000000000000..3df2fef72e0b --- /dev/null +++ b/tools/dotcms-cli/api-data-model/src/test/resources/docker-compose.yaml @@ -0,0 +1,72 @@ +version: '3' +services: + postgres: + image: postgres:15 + ports: + - "5432:5432" + environment: + - POSTGRES_USER=dotcms + - POSTGRES_PASSWORD=dotcms + - POSTGRES_DB=dotcms + command: postgres -c 'max_connections=400' -c 'shared_buffers=128MB' + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - dotcms-net + + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:7.16.1 + ports: + - "9200:9200" + environment: + - discovery.type=single-node + - cluster.name=elastic-cluster + - bootstrap.memory_lock=true + - "ES_JAVA_OPTS=-Xms512m -Xmx1g" + ulimits: + memlock: + soft: -1 # Set memlock to unlimited (no soft or hard limit) + hard: -1 + nofile: + soft: 65536 # Maximum number of open files for the opensearch user - set to at least 65536 + hard: 65536 + volumes: + - elasticsearch-data:/usr/share/elasticsearch/data + networks: + - dotcms-net + + + dotcms: + image: ${DOTCMS_IMAGE} # it must be the same version built in the previous step + environment: + CMS_JAVA_OPTS: '-Xmx1g ' + LANG: 'C.UTF-8' + TZ: 'UTC' + DB_BASE_URL: "jdbc:postgresql://postgres/dotcms" + DB_USERNAME: "dotcms" + DB_PASSWORD: "dotcms" + DOT_ES_AUTH_BASIC_PASSWORD: 'admin' + DOT_INITIAL_ADMIN_PASSWORD: 'admin' + DOT_ES_ENDPOINTS: 'http://elasticsearch:9200' + #CUSTOM_STARTER_URL: 'https://repo.dotcms.com/artifactory/libs-release-local/com/dotcms/starter/20211201/starter-20211201.zip' + depends_on: + - elasticsearch + - postgres + volumes: + - dotcms-shared:/data/shared + - type: bind + source: ${DOTCMS_LICENSE_FILE} + target: /data/local/dotsecure/license/license.dat + networks: + - dotcms-net + ports: + - "8080:8080" + - "8443:8443" + +volumes: + postgres-data: + elasticsearch-data: + dotcms-shared: + +networks: + dotcms-net: \ No newline at end of file diff --git a/tools/dotcms-cli/cli/pom.xml b/tools/dotcms-cli/cli/pom.xml index 4a132527ad88..9fe1315fd751 100644 --- a/tools/dotcms-cli/cli/pom.xml +++ b/tools/dotcms-cli/cli/pom.xml @@ -22,7 +22,10 @@ io.quarkus.platform 2.9.1.Final 3.0.0-M5 - + 1.17.6 + false + false + true @@ -70,23 +73,53 @@ mockito-junit-jupiter test - + org.jboss.logging jboss-logging + com.starxg java-keytar 1.0.0 + + + io.rest-assured + rest-assured + test + + @@ -118,52 +151,44 @@ maven-surefire-plugin ${surefire-plugin.version} + ${test.failure.ignore} + ${skip.surefire.tests} org.jboss.logmanager.LogManager ${maven.home} - + native diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/HybridServiceManagerImpl.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/HybridServiceManagerImpl.java index abf419847579..1bf99ea94dd0 100644 --- a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/HybridServiceManagerImpl.java +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/HybridServiceManagerImpl.java @@ -1,13 +1,11 @@ package com.dotcms.api.client; +import com.dotcms.api.client.SecurePasswordStore.StoreSecureException; import com.dotcms.model.annotation.SecuredPassword; import com.dotcms.model.config.CredentialsBean; import com.dotcms.model.config.ServiceBean; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.starxg.keytar.Keytar; -import com.starxg.keytar.KeytarException; import org.jboss.logging.Logger; - import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import java.io.IOException; @@ -31,22 +29,26 @@ public class HybridServiceManagerImpl implements ServiceManager { @Inject Logger logger; + @Inject + SecurePasswordStore passwordStore; + @Override @CanIgnoreReturnValue public ServiceManager persist(ServiceBean service) throws IOException { - final Keytar keytar = Keytar.getInstance(); CredentialsBean credentialsBean = service.credentials(); if (null != credentialsBean && null != credentialsBean.user() && null != credentialsBean.token()) { //We need to split the info and save in the KeyChain the authentication token try { //First store the sensitive data in the keychain - keytar.setPassword(service.name(), credentialsBean.user(), new String(credentialsBean.token())); + passwordStore.setPassword(service.name(), credentialsBean.user(), new String(credentialsBean.token())); // then strip any sensitive data from the info that is going to be saved into the yml/text file CredentialsBean strippedTokenBean = CredentialsBean.builder().from(credentialsBean).token(EMPTY_TOKEN).build(); ServiceBean strippedCredentialsBean = ServiceBean.builder().from(service).credentials(strippedTokenBean).build(); defaultManager.persist(strippedCredentialsBean); - } catch (KeytarException e) { + } catch (StoreSecureException e) { logger.warn(String.format("Unable to persist credentials for service [%s] using the Key-Chain. access credentials will be stored as plain text.", service.name()), e); + //Now upon error we need to fall back to YML file for storage + defaultManager.persist(service); } return this; } @@ -57,7 +59,6 @@ public ServiceManager persist(ServiceBean service) throws IOException { @Override public List services() { - final Keytar keytar = Keytar.getInstance(); //Retrieve the beans stored in the yml file final List services = defaultManager.services(); final List beans = new ArrayList<>(services.size()); @@ -68,18 +69,20 @@ public List services() { logger.info(String.format("Service [%s] is missing credentials.", service.name())); } else { try { - final String token = keytar.getPassword(service.name(), credentialsBean.user()); + final String token = passwordStore.getPassword(service.name(), credentialsBean.user()); if (null != token) { CredentialsBean newCredentialsBean = CredentialsBean.builder().from(credentialsBean).token(token.toCharArray()).build(); ServiceBean bean = ServiceBean.builder().from(service).credentials(newCredentialsBean).build(); beans.add(bean); continue; } - } catch (Exception e) { - logger.error(String.format("Unable to recover token from key-chain for service [%s]", service.name()), e); + } catch (StoreSecureException e) { + logger.warn(String.format("Unable to recover token from key-chain for service [%s]", service.name()), e); + //Upon error, we need to return the original list + return services; } } - //This should take of any bean that could have found an error or missing credentials + //This should take off any bean that could have found an error or missing credentials beans.add(service); } //This makes it return an immutable list @@ -89,14 +92,13 @@ public List services() { @Override @CanIgnoreReturnValue public ServiceManager removeAll() { - final Keytar keytar = Keytar.getInstance(); List services = defaultManager.services(); for (ServiceBean service : services) { CredentialsBean credentialsBean = service.credentials(); if (null != credentialsBean && null != credentialsBean.user()) { try { - keytar.deletePassword(service.name(), credentialsBean.user()); - } catch (KeytarException e) { + passwordStore.deletePassword(service.name(), credentialsBean.user()); + } catch (StoreSecureException e) { logger.warn(String.format("Unable to delete token from key-chain for service [%s]", service.name()), e); } } diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/KeyTarPasswordStoreImpl.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/KeyTarPasswordStoreImpl.java new file mode 100644 index 000000000000..74bf083ab44b --- /dev/null +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/KeyTarPasswordStoreImpl.java @@ -0,0 +1,48 @@ +package com.dotcms.api.client; + +import com.starxg.keytar.Keytar; +import com.starxg.keytar.KeytarException; +import javax.enterprise.context.ApplicationScoped; + +/** + * KeyTarPasswordStoreImpl implements the SecurePasswordStore interface to securely store passwords using the system keychain. + * It uses the Keytar library to interface with the native keychain/credential manager of the operating system. + * The setPassword method stores the password for the given service and account. + * It wraps any KeytarException in a StoreSecureException. + * The getPassword method retrieves the password for the given service and account. + * It wraps any KeytarException in a StoreSecureException. + * The deletePassword method deletes the password for the given service and account. + * It wraps any KeytarException in a StoreSecureException. + */ +@ApplicationScoped +public class KeyTarPasswordStoreImpl implements SecurePasswordStore { + + Keytar keytar = Keytar.getInstance(); + + @Override + public void setPassword(String service, String account, String password) throws StoreSecureException { + try { + keytar.setPassword(service, account, password); + } catch (KeytarException e) { + throw new StoreSecureException("Failure saving password securely",e); + } + } + + @Override + public String getPassword(String service, String account) throws StoreSecureException { + try { + return keytar.getPassword(service, account); + } catch (KeytarException e) { + throw new StoreSecureException("Failure retrieving password from secure storage",e); + } + } + + @Override + public void deletePassword(String service, String account) throws StoreSecureException { + try { + keytar.deletePassword(service, account); + } catch (KeytarException e) { + throw new StoreSecureException("Failure deleting password from secure storage",e); + } + } +} diff --git a/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/SecurePasswordStore.java b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/SecurePasswordStore.java new file mode 100644 index 000000000000..73b4f5328b74 --- /dev/null +++ b/tools/dotcms-cli/cli/src/main/java/com/dotcms/api/client/SecurePasswordStore.java @@ -0,0 +1,29 @@ +package com.dotcms.api.client; + +/** + * SecurePasswordStore provides an interface for securely storing and retrieving passwords. + * The setPassword method stores a password for the given service and account. + * The getPassword method retrieves the password for the given service and account. + * The deletePassword method deletes the stored password for the given service and account. + * StoreSecureException is thrown if there are any errors storing, retrieving or deleting passwords. + * It extends Exception and provides constructors to set the error message and optional cause Throwable. + */ +public interface SecurePasswordStore { + + void setPassword(String service, String account, String password) throws StoreSecureException; + + String getPassword(String service, String account) throws StoreSecureException; + + void deletePassword(String service, String account) throws StoreSecureException; + + class StoreSecureException extends java.lang.Exception { + public StoreSecureException(String message) { + super(message); + } + + public StoreSecureException(String message, Throwable cause) { + super(message, cause); + } + } + +} diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/ContainerResource.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/ContainerResource.java new file mode 100644 index 000000000000..9afe255a6428 --- /dev/null +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/ContainerResource.java @@ -0,0 +1,78 @@ +package com.dotcms; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import io.quarkus.test.junit.QuarkusTest; +import org.apache.http.HttpStatus; +import org.eclipse.microprofile.config.ConfigProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; + +import java.io.File; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +/** + * ContainerResource implements the QuarkusTestResourceLifecycleManager interface to manage starting and stopping the Docker containers for testing. + * The class has constants for the ports of the Postgres, Elasticsearch, and dotCMS services exposed by Docker Compose. + * The static initializer block reads test configuration from MicroProfile Config and creates a DockerComposeContainer. + * It configures exposed services, environment variables, and logging. + * The start() method starts the Docker Compose container and returns a map with the dotCMS URL for tests to use. + * The stop() method stops the Docker Compose container when tests are complete. + */ +@QuarkusTest +public class ContainerResource implements QuarkusTestResourceLifecycleManager { + + private static final int POSTGRES_SERVICE_PORT; + private static final int ELASTICSEARCH_SERVICE_PORT; + private static final int DOTCMS_SERVICE_PORT; + private static final Logger LOGGER = LoggerFactory.getLogger(ContainerResource.class); + + static DockerComposeContainer COMPOSE_CONTAINER; + + static { + + POSTGRES_SERVICE_PORT = Integer.parseInt(ConfigProvider.getConfig().getValue("testcontainers.postgres.service.port", String.class)); + ELASTICSEARCH_SERVICE_PORT = Integer.parseInt(ConfigProvider.getConfig().getValue("testcontainers.elasticsearch.service.port", String.class)); + DOTCMS_SERVICE_PORT = Integer.parseInt(ConfigProvider.getConfig().getValue("testcontainers.dotcms.service.port", String.class)); + + final boolean isLoggerEnabled = Boolean.parseBoolean(ConfigProvider.getConfig().getValue("testcontainers.logger.enabled", String.class)); + final boolean isLocalComposeEnabled = Boolean.parseBoolean(ConfigProvider.getConfig().getValue("testcontainers.docker.compose.local.enabled", String.class)); + final String dockerImage = ConfigProvider.getConfig().getValue("testcontainers.docker.image", String.class); + final String dockerComposeFile = ConfigProvider.getConfig().getValue("testcontainers.docker.compose.file", String.class); + final String dotcmsLicenseFile = ConfigProvider.getConfig().getValue("testcontainers.dotcms.license.file", String.class); + final long dockerComposeStartupTimeout = ConfigProvider.getConfig().getValue("testcontainers.docker.compose.startup.timeout", Long.class); + + DockerComposeContainer dockerComposeContainer = new DockerComposeContainer("dotcms-env", new File(dockerComposeFile)); + dockerComposeContainer.withExposedService("postgres", POSTGRES_SERVICE_PORT, Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(dockerComposeStartupTimeout))); + dockerComposeContainer.withExposedService("elasticsearch", ELASTICSEARCH_SERVICE_PORT, Wait.forHttp("/").forPort(ELASTICSEARCH_SERVICE_PORT).forStatusCode(HttpStatus.SC_OK)); + dockerComposeContainer.withExposedService("dotcms", DOTCMS_SERVICE_PORT, Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(dockerComposeStartupTimeout))); + dockerComposeContainer.withEnv("DOTCMS_IMAGE", dockerImage); + dockerComposeContainer.withEnv("DOTCMS_LICENSE_FILE", dotcmsLicenseFile); + + if (isLoggerEnabled) { + dockerComposeContainer.withLogConsumer("dotcms", new Slf4jLogConsumer(LOGGER)); + } + + dockerComposeContainer.withLocalCompose(isLocalComposeEnabled); // Needs to be false to run in macOS + COMPOSE_CONTAINER = dockerComposeContainer; + } + + @Override + public Map start() { + + COMPOSE_CONTAINER.start(); + final Map conf = new HashMap<>(); + conf.put("%test.dotcms.url", COMPOSE_CONTAINER.getServiceHost("dotcms", DOTCMS_SERVICE_PORT) + ":" + COMPOSE_CONTAINER.getServicePort("dotcms", DOTCMS_SERVICE_PORT)); + + return conf; + } + + @Override + public void stop() { + COMPOSE_CONTAINER.stop(); + } +} diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/ContainerTest.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/ContainerTest.java new file mode 100644 index 000000000000..6f524548470a --- /dev/null +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/ContainerTest.java @@ -0,0 +1,14 @@ +package com.dotcms; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; + +import static io.restassured.RestAssured.given; + +@QuarkusTest +@QuarkusTestResource(ContainerResource.class) +public class ContainerTest { +} + diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/HybridServiceManagerTest.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/HybridServiceManagerTest.java index 8c649212091d..9dbec7ccf7ee 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/HybridServiceManagerTest.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/HybridServiceManagerTest.java @@ -6,13 +6,14 @@ import com.starxg.keytar.Keytar; import com.starxg.keytar.KeytarException; import io.quarkus.test.junit.QuarkusTest; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import javax.inject.Inject; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -import javax.inject.Inject; -import java.io.IOException; -import java.util.Optional; +import org.wildfly.common.Assert; @QuarkusTest class HybridServiceManagerTest { @@ -29,36 +30,32 @@ public void setupTest() throws IOException { serviceManager.removeAll().persist(ServiceBean.builder().name("default").active(true).build()); } + /** + * This test is here to demonstrate that even if the keytar library is not working on the CI server, we still can rely on the yml file for storage. + * This service manager is injected with a Mock that simulates the keytar library not working. + * @throws IOException + */ @Test - void Test_Persist_Then_Recover() throws IOException, KeytarException { - - Keytar instance = Keytar.getInstance(); - final ServiceBean serviceBeanDefault = ServiceBean.builder().name("default") - .active(false) + void Test_Service_Manager_Resilence() throws IOException { + serviceManager.removeAll(); + Assert.assertTrue(serviceManager.services().isEmpty()); + final String key = "resilece-test"; + final ServiceBean serviceBean = ServiceBean.builder().name(key) + .active(true) .credentials( CredentialsBean.builder().user("admin") .token(FAKE_TOKEN).build()) .build(); - serviceManager.persist(serviceBeanDefault); - String pass = instance.getPassword("default","admin"); - String fakeToken = new String(FAKE_TOKEN); - Assertions.assertEquals(fakeToken,pass); - Optional optional = serviceManager.services().stream().filter(serviceBean -> "default".equals(serviceBean.name())).findFirst(); + serviceManager.persist(serviceBean); + final List services = serviceManager.services(); + final Optional optional = serviceManager.services().stream().filter(bean -> key.equals(bean.name())).findFirst(); Assertions.assertTrue(optional.isPresent()); - ServiceBean bean = optional.get(); - Assertions.assertEquals("default",bean.name()); + final ServiceBean bean = optional.get(); + Assertions.assertEquals(key,bean.name()); Assertions.assertNotNull(bean.credentials()); Assertions.assertEquals("admin", bean.credentials().user()); - Assertions.assertEquals(fakeToken, new String(bean.credentials().token())); - - serviceManager.removeAll(); - - optional = serviceManager.services().stream().filter(serviceBean -> "default".equals(serviceBean.name())).findFirst(); - Assertions.assertFalse(optional.isPresent(),"service instance should have been removed."); - Assertions.assertNull(instance.getPassword("default","admin")); - Assertions.assertFalse(serviceManager.selected().isPresent()); - + Assertions.assertNotNull(bean.credentials().token()); + Assertions.assertEquals(new String(FAKE_TOKEN), new String(bean.credentials().token())); } - } diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/MockUnsupportedSecurePasswordStoreImpl.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/MockUnsupportedSecurePasswordStoreImpl.java new file mode 100644 index 000000000000..ea19d810deb6 --- /dev/null +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/MockUnsupportedSecurePasswordStoreImpl.java @@ -0,0 +1,29 @@ +package com.dotcms.api.client; + +import io.quarkus.arc.Priority; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Alternative; + +/** + * When running on a non-OSX system, the KeyChain is not always available, so we need to verify if our code can still survive such scenario + */ +@Alternative +@Priority(1) +@ApplicationScoped +public class MockUnsupportedSecurePasswordStoreImpl implements SecurePasswordStore { + + @Override + public void setPassword(String service, String account, String password) throws StoreSecureException { + throw new StoreSecureException("Unsupported operation"); + } + + @Override + public String getPassword(String service, String account) throws StoreSecureException { + throw new StoreSecureException("Unsupported operation"); + } + + @Override + public void deletePassword(String service, String account) throws StoreSecureException { + throw new StoreSecureException("Unsupported operation"); + } +} diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PullServiceTest.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PullServiceIntegrationTest.java similarity index 99% rename from tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PullServiceTest.java rename to tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PullServiceIntegrationTest.java index 44effad75212..585ba7147384 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PullServiceTest.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PullServiceIntegrationTest.java @@ -29,7 +29,7 @@ import org.junit.jupiter.api.Test; @QuarkusTest -class PullServiceTest extends FilesTestHelper { +class PullServiceIntegrationTest extends FilesTestHelper { @Inject AuthenticationContext authenticationContext; diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PushServiceTest.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PushServiceIntegrationTest.java similarity index 99% rename from tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PushServiceTest.java rename to tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PushServiceIntegrationTest.java index 0e875110a35f..0405ac14eb5f 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PushServiceTest.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/api/client/files/PushServiceIntegrationTest.java @@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test; @QuarkusTest -class PushServiceTest extends FilesTestHelper { +class PushServiceIntegrationTest extends FilesTestHelper { @Inject AuthenticationContext authenticationContext; diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/InstanceCommandTest.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/InstanceCommandTest.java index 16cc988a81b1..ff655a1b0c21 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/InstanceCommandTest.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/InstanceCommandTest.java @@ -2,14 +2,18 @@ import com.dotcms.model.config.ServiceBean; import io.quarkus.test.junit.QuarkusTest; -import org.junit.jupiter.api.*; -import picocli.CommandLine; -import picocli.CommandLine.ExitCode; - import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Optional; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import picocli.CommandLine; +import picocli.CommandLine.ExitCode; @QuarkusTest public class InstanceCommandTest extends CommandTest { diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/StatusCommandTest.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/StatusCommandTest.java index 27fcbb40f011..02deb6120932 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/StatusCommandTest.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/StatusCommandTest.java @@ -1,23 +1,18 @@ package com.dotcms.cli.command; import com.dotcms.api.AuthenticationContext; -import com.dotcms.api.client.ServiceManager; import com.dotcms.model.config.CredentialsBean; import com.dotcms.model.config.ServiceBean; -import io.quarkus.picocli.runtime.PicocliCommandLineFactory; import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.*; +import picocli.CommandLine; +import picocli.CommandLine.ExitCode; + +import javax.inject.Inject; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Optional; -import javax.inject.Inject; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import picocli.CommandLine; -import picocli.CommandLine.ExitCode; @QuarkusTest diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandTest.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandIntegrationTest.java similarity index 99% rename from tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandTest.java rename to tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandIntegrationTest.java index eb4ea7389b31..d0e1b89e9db1 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandTest.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/contenttype/ContentTypeCommandIntegrationTest.java @@ -37,7 +37,7 @@ import picocli.CommandLine.ExitCode; @QuarkusTest -class ContentTypeCommandTest extends CommandTest { +class ContentTypeCommandIntegrationTest extends CommandTest { @Inject AuthenticationContext authenticationContext; diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesLsCommandTest.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesLsCommandIntegrationTest.java similarity index 99% rename from tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesLsCommandTest.java rename to tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesLsCommandIntegrationTest.java index 0cc2575db25a..92434853f244 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesLsCommandTest.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesLsCommandIntegrationTest.java @@ -14,7 +14,7 @@ import picocli.CommandLine.ExitCode; @QuarkusTest -class FilesLsCommandTest extends CommandTest { +class FilesLsCommandIntegrationTest extends CommandTest { @Inject AuthenticationContext authenticationContext; diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesPullCommandTest.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesPullCommandIntegrationTest.java similarity index 100% rename from tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesPullCommandTest.java rename to tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesPullCommandIntegrationTest.java diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesTreeCommandTest.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesTreeCommandIntegrationTest.java similarity index 99% rename from tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesTreeCommandTest.java rename to tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesTreeCommandIntegrationTest.java index a00adcd9e13b..5d35cbd66c95 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesTreeCommandTest.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/files/FilesTreeCommandIntegrationTest.java @@ -16,7 +16,7 @@ import picocli.CommandLine.ExitCode; @QuarkusTest -public class FilesTreeCommandTest extends CommandTest { +public class FilesTreeCommandIntegrationTest extends CommandTest { @Inject AuthenticationContext authenticationContext; diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/language/LanguageCommandTest.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/language/LanguageCommandIntegrationTest.java similarity index 98% rename from tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/language/LanguageCommandTest.java rename to tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/language/LanguageCommandIntegrationTest.java index 716a824bcfbe..4d1c7ca7f87e 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/language/LanguageCommandTest.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/language/LanguageCommandIntegrationTest.java @@ -11,6 +11,13 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import picocli.CommandLine; +import picocli.CommandLine.ExitCode; + +import javax.inject.Inject; import java.io.File; import java.io.IOException; import java.io.PrintWriter; @@ -22,17 +29,9 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.UUID; import java.util.stream.Stream; -import javax.inject.Inject; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.wildfly.common.Assert; -import picocli.CommandLine; -import picocli.CommandLine.ExitCode; @QuarkusTest -class LanguageCommandTest extends CommandTest { - +class LanguageCommandIntegrationTest extends CommandTest { @Inject AuthenticationContext authenticationContext; @Inject @@ -362,7 +361,7 @@ void Test_Pull_Same_Language_Multiple_Times() throws IOException { final StringWriter writer = new StringWriter(); try (PrintWriter out = new PrintWriter(writer)) { commandLine.setOut(out); - final String lang = "en-US"; + final String lang = "en-US".toLowerCase(); for (int i=0; i<= 5; i++) { final int status = commandLine.execute(LanguageCommand.NAME, LanguagePull.NAME, lang, "--workspace", workspace.root().toString()); Assertions.assertEquals(CommandLine.ExitCode.OK, status); @@ -371,7 +370,7 @@ void Test_Pull_Same_Language_Multiple_Times() throws IOException { final String fileName = String.format("%s.json", lang); final Path path = Path.of(workspace.languages().toString(), fileName); - Assert.assertTrue(Files.exists(path)); + Assertions.assertTrue(Files.exists(path),String.format("The file [%s] should exist", path)); try (Stream walk = Files.walk(workspace.languages())) { long count = walk.filter(p -> Files.isRegularFile(p) && p.getFileName().toString() diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/site/SiteCommandTest.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/site/SiteCommandIntegrationTest.java similarity index 99% rename from tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/site/SiteCommandTest.java rename to tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/site/SiteCommandIntegrationTest.java index 44f631c087df..c213414ca392 100644 --- a/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/site/SiteCommandTest.java +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/cli/command/site/SiteCommandIntegrationTest.java @@ -26,7 +26,7 @@ import picocli.CommandLine.ExitCode; @QuarkusTest -class SiteCommandTest extends CommandTest { +class SiteCommandIntegrationTest extends CommandTest { @ConfigProperty(name = "com.dotcms.starter.site", defaultValue = "default") String siteName; diff --git a/tools/dotcms-cli/cli/src/test/java/com/dotcms/config/PropertiesConfigSource.java b/tools/dotcms-cli/cli/src/test/java/com/dotcms/config/PropertiesConfigSource.java new file mode 100644 index 000000000000..9b05b9d58867 --- /dev/null +++ b/tools/dotcms-cli/cli/src/test/java/com/dotcms/config/PropertiesConfigSource.java @@ -0,0 +1,53 @@ +package com.dotcms.config; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.Set; + +/** + * This class is used to load the properties from the application.properties file. + * It is needed to inject the properties into a static block. + */ +public class PropertiesConfigSource implements ConfigSource { + private Properties properties; + + public PropertiesConfigSource() { + loadPropertiesFromFile(); + } + + /** + * Load the properties from the application.properties file. + */ + private void loadPropertiesFromFile() { + properties = new Properties(); + try (InputStream inputStream = getClass().getResourceAsStream("/application.properties")) { + properties.load(inputStream); + } catch (IOException e) { + // Handle the exception or log the error + } + } + + @Override + public Set getPropertyNames() { + return properties.stringPropertyNames(); + } + + @Override + public String getValue(String propertyName) { + return properties.getProperty(propertyName); + } + + @Override + public String getName() { + return "DiskConfigSource"; + } + + @Override + public int getOrdinal() { + return 100; // The ordinal of the config source. Config sources with a lower ordinal are tried first. + } + +} diff --git a/tools/dotcms-cli/cli/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/tools/dotcms-cli/cli/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource new file mode 100644 index 000000000000..93e05c9c536f --- /dev/null +++ b/tools/dotcms-cli/cli/src/test/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource @@ -0,0 +1 @@ +com.dotcms.config.PropertiesConfigSource \ No newline at end of file diff --git a/tools/dotcms-cli/cli/src/test/resources/application.properties b/tools/dotcms-cli/cli/src/test/resources/application.properties new file mode 100644 index 000000000000..53f9b5459c12 --- /dev/null +++ b/tools/dotcms-cli/cli/src/test/resources/application.properties @@ -0,0 +1,9 @@ +testcontainers.logger.enabled=true +testcontainers.docker.compose.local.enabled=false +testcontainers.docker.image=${DOTCMS_IMAGE:dotcms/dotcms:master_latest_SNAPSHOT} +testcontainers.docker.compose.file=src/test/resources/docker-compose.yaml +testcontainers.docker.compose.startup.timeout=120 +testcontainers.dotcms.license.file=${DOTCMS_LICENSE_FILE} +testcontainers.dotcms.service.port=8080 +testcontainers.elasticsearch.service.port=9200 +testcontainers.postgres.service.port=5432 \ No newline at end of file diff --git a/tools/dotcms-cli/cli/src/test/resources/docker-compose.yaml b/tools/dotcms-cli/cli/src/test/resources/docker-compose.yaml new file mode 100644 index 000000000000..b1b7e85ece00 --- /dev/null +++ b/tools/dotcms-cli/cli/src/test/resources/docker-compose.yaml @@ -0,0 +1,73 @@ +version: '3' +services: + postgres: + image: postgres:15 + ports: + - "5432:5432" + environment: + - POSTGRES_USER=dotcms + - POSTGRES_PASSWORD=dotcms + - POSTGRES_DB=dotcms + command: postgres -c 'max_connections=400' -c 'shared_buffers=128MB' + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - dotcms-net + + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:7.16.1 + ports: + - "9200:9200" + environment: + - discovery.type=single-node + - cluster.name=elastic-cluster + - bootstrap.memory_lock=true + - "ES_JAVA_OPTS=-Xms512m -Xmx1g" + ulimits: + memlock: + soft: -1 # Set memlock to unlimited (no soft or hard limit) + hard: -1 + nofile: + soft: 65536 # Maximum number of open files for the opensearch user - set to at least 65536 + hard: 65536 + volumes: + - elasticsearch-data:/usr/share/elasticsearch/data + networks: + - dotcms-net + + + dotcms: + image: ${DOTCMS_IMAGE} # it must be the same version built in the previous step + environment: + CMS_JAVA_OPTS: '-Xmx1g ' + LANG: 'C.UTF-8' + TZ: 'UTC' + DB_BASE_URL: "jdbc:postgresql://postgres/dotcms" + DB_USERNAME: "dotcms" + DB_PASSWORD: "dotcms" + DOT_ES_AUTH_BASIC_PASSWORD: 'admin' + DOT_INITIAL_ADMIN_PASSWORD: 'admin' + DOT_ES_ENDPOINTS: 'http://elasticsearch:9200' + #CUSTOM_STARTER_URL: 'https://repo.dotcms.com/artifactory/libs-release-local/com/dotcms/starter/20211201/starter-20211201.zip' + depends_on: + - elasticsearch + - postgres + volumes: + - dotcms-shared:/data/shared + - type: bind + source: ${DOTCMS_LICENSE_FILE} + target: /data/local/dotsecure/license/license.dat + networks: + - dotcms-net + ports: + - "8080:8080" + - "8443:8443" + +volumes: + postgres-data: + elasticsearch-data: + dotcms-shared: +# license: + +networks: + dotcms-net: \ No newline at end of file diff --git a/tools/dotcms-cli/pom.xml b/tools/dotcms-cli/pom.xml index 2a8012c091e1..61295f0f0720 100644 --- a/tools/dotcms-cli/pom.xml +++ b/tools/dotcms-cli/pom.xml @@ -40,6 +40,11 @@ quarkus-maven-plugin ${quarkus.platform.version} + + org.apache.maven.plugins + maven-failsafe-plugin + ${surefire-plugin.version} +
ModuleReport