mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2026-09-09 18:02:54 +00:00
[ACS-11294] added search indexing check in ACA (#5091)
* [ACS-11294] added search indexing check in ACA * [ACS-11294] small changes to the script * [ACS-11293] deleted commander from the script and added .env file check * [ACS-11294] added search indexing action after deploy ACS * [ACS-11294] check added to all test suites and deleted hard coded credentials * [ACS-11294] sonar cloud fixes * [ACS-11294] search indexing fix 1 * [ACS-11294] review fixes 1 * [ACS-11294] review fixes 2
This commit is contained in:
@@ -49,3 +49,9 @@ runs:
|
||||
fi
|
||||
fi
|
||||
echo "TAG_NPM=${TAG_NPM}" >> $GITHUB_ENV
|
||||
|
||||
- name: node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
||||
@@ -36,11 +36,8 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
- run: npm ci
|
||||
|
||||
- name: upload npm logs
|
||||
@@ -67,11 +64,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
- uses: ./.github/actions/before-install
|
||||
- run: npm ci
|
||||
- name: upload npm logs
|
||||
@@ -99,11 +91,6 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
- uses: ./.github/actions/before-install
|
||||
- run: npm ci
|
||||
- name: upload npm logs
|
||||
@@ -181,12 +168,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Deploy local ACS
|
||||
id: deploy-local-acs
|
||||
uses: ./.github/actions/deploy-local-acs
|
||||
@@ -220,6 +201,12 @@ jobs:
|
||||
- name: Before e2e
|
||||
uses: ./.github/actions/before-e2e
|
||||
|
||||
- name: Check Search Indexing
|
||||
continue-on-error: true
|
||||
run: npm run ci:check-search-indexing
|
||||
env:
|
||||
ADMIN_PASSWORD_SCRIPT: ${{ env.ADMIN_PASSWORD }}
|
||||
|
||||
- name: Before playwright (Chromium for PR)
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: npm run ci:playwright:install
|
||||
|
||||
@@ -60,11 +60,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
- uses: ./.github/actions/setup
|
||||
- run: npm ci
|
||||
- run: npm run affected:lint -- --base=origin/develop
|
||||
- run: npm run stylelint
|
||||
@@ -86,11 +82,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
- uses: ./.github/actions/before-install
|
||||
- run: npm ci
|
||||
- run: npm run affected:test -- --browsers=ChromeHeadless --watch=false $TEST_OPTS --base=origin/develop
|
||||
@@ -109,11 +100,6 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
- uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_tag: ${{ inputs.npm-tag }}
|
||||
@@ -146,11 +132,6 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
- uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_tag: ${{ inputs.npm-tag }}
|
||||
@@ -183,11 +164,6 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
- uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_tag: ${{ inputs.npm-tag }}
|
||||
@@ -222,12 +198,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
||||
- uses: ./.github/actions/setup
|
||||
with:
|
||||
npm_tag: ${{ inputs.npm-tag }}
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@
|
||||
"ci:audit": "adf-cli audit -d docs/audit",
|
||||
"ci:licenses": "adf-cli licenses -d docs/licences",
|
||||
"ci:changelog": "adf-cli changelog -o docs/changelog --exclude=\"bot\\|Alfresco Build User\\|alfresco-build\"",
|
||||
"ci:check-env": "adf-cli check-cs-env --host $BASE_URL -u $ADMIN_EMAIL -p $ADMIN_PASSWORD"
|
||||
"ci:check-env": "adf-cli check-cs-env --host $BASE_URL -u $ADMIN_EMAIL -p $ADMIN_PASSWORD",
|
||||
"ci:check-search-indexing": "node --env-file-if-exists=.env ./scripts/ci/check-env/check-search-indexing.mjs"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/*
|
||||
* Copyright © 2005-2026 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* License rights for this program may be obtained from Hyland Software, Inc.
|
||||
* pursuant to a written agreement and any use of this program without such an
|
||||
* agreement is prohibited.
|
||||
*/
|
||||
|
||||
import { AlfrescoApi, NodesApi, SearchApi, PeopleApi, GroupsApi, CategoriesApi } from '@alfresco/js-api';
|
||||
import { exit } from 'node:process';
|
||||
|
||||
function generateRandomString(length = 8) {
|
||||
const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const randomBytes = new Uint32Array(length);
|
||||
crypto.getRandomValues(randomBytes);
|
||||
return Array.from(randomBytes, (byte) => charSet[byte % charSet.length]).join('');
|
||||
}
|
||||
|
||||
const INDEXING_TIMEOUT_SECONDS = 80;
|
||||
const RETRY_INTERVAL_SECONDS = 2;
|
||||
const TEST_PREFIX = 'search-indexing-check';
|
||||
|
||||
let alfrescoJsApi;
|
||||
let adminAlfrescoJsApi;
|
||||
let nodesApi;
|
||||
let searchApi;
|
||||
let peopleApi;
|
||||
let groupsApi;
|
||||
let categoriesApi;
|
||||
|
||||
async function main() {
|
||||
const host = process.env.BASE_URL;
|
||||
const username = process.env.HR_USER;
|
||||
const password = process.env.HR_USER_PASSWORD;
|
||||
const adminUsername = process.env.ADMIN_EMAIL;
|
||||
const adminPassword = process.env.ADMIN_PASSWORD_SCRIPT;
|
||||
|
||||
if (!host || !username || !password) {
|
||||
console.error('[ 🔍 Search Indexing Check ] Missing required environment variables: BASE_URL, HR_USER, HR_USER_PASSWORD');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
console.info(`[ 🔍 Search Indexing Check ] Checking search indexing on: ${host}`);
|
||||
|
||||
const oauthConfig = {
|
||||
authType: 'BASIC',
|
||||
hostBpm: host,
|
||||
hostEcm: host,
|
||||
provider: 'ECM',
|
||||
contextRoot: 'alfresco'
|
||||
};
|
||||
|
||||
try {
|
||||
alfrescoJsApi = new AlfrescoApi(oauthConfig);
|
||||
await alfrescoJsApi.login(username, password);
|
||||
console.info('[ 🔍 Search Indexing Check ] Successfully logged in as regular user.');
|
||||
} catch (error) {
|
||||
console.error('[ 🔍 Search Indexing Check ] Regular user login failed:', error?.message || error);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
nodesApi = new NodesApi(alfrescoJsApi);
|
||||
searchApi = new SearchApi(alfrescoJsApi);
|
||||
|
||||
let adminSkipReason = null;
|
||||
|
||||
if (adminUsername && adminPassword) {
|
||||
try {
|
||||
adminAlfrescoJsApi = new AlfrescoApi(oauthConfig);
|
||||
await adminAlfrescoJsApi.login(adminUsername, adminPassword);
|
||||
console.info('[ 🔍 Search Indexing Check ] Successfully logged in as admin user.');
|
||||
} catch (error) {
|
||||
adminSkipReason = 'admin login failed';
|
||||
console.warn('[ 🔍 Search Indexing Check ] Admin login failed, skipping user/group/category checks:', error?.message || error);
|
||||
}
|
||||
} else {
|
||||
adminSkipReason = 'admin credentials not provided';
|
||||
console.warn('[ 🔍 Search Indexing Check ] Admin credentials not provided, skipping user/group/category checks.');
|
||||
}
|
||||
|
||||
if (adminAlfrescoJsApi) {
|
||||
peopleApi = new PeopleApi(adminAlfrescoJsApi);
|
||||
groupsApi = new GroupsApi(adminAlfrescoJsApi);
|
||||
categoriesApi = new CategoriesApi(adminAlfrescoJsApi);
|
||||
}
|
||||
|
||||
const uniqueId = Date.now();
|
||||
const results = {
|
||||
file: await checkFileIndexing(uniqueId),
|
||||
user: adminAlfrescoJsApi ? await checkUserIndexing(uniqueId) : { skipped: adminSkipReason },
|
||||
group: adminAlfrescoJsApi ? await checkGroupIndexing(uniqueId) : { skipped: adminSkipReason },
|
||||
category: adminAlfrescoJsApi ? await checkCategoryIndexing(uniqueId) : { skipped: adminSkipReason }
|
||||
};
|
||||
|
||||
printSummary(results);
|
||||
}
|
||||
|
||||
function printSummary(results) {
|
||||
console.info('[ 🔍 Search Indexing Check ] === Summary ===');
|
||||
|
||||
const labels = {
|
||||
file: 'File',
|
||||
user: 'User',
|
||||
group: 'Group',
|
||||
category: 'Category'
|
||||
};
|
||||
|
||||
let hasFailure = false;
|
||||
|
||||
for (const [key, result] of Object.entries(results)) {
|
||||
if (result?.skipped) {
|
||||
console.info(`[ 🔍 Search Indexing Check ] ⏭️ ${labels[key]}: skipped (${result.skipped}).`);
|
||||
} else if (result.indexed) {
|
||||
console.info(`[ 🔍 Search Indexing Check ] ✅ ${labels[key]} "${result.name}" was indexed within ${INDEXING_TIMEOUT_SECONDS}s.`);
|
||||
} else {
|
||||
console.error(`[ 🔍 Search Indexing Check ] ❌ ${labels[key]} "${result.name}" was NOT indexed within ${INDEXING_TIMEOUT_SECONDS}s.`);
|
||||
hasFailure = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFailure) {
|
||||
console.warn(
|
||||
'[ 🔍 Search Indexing Check ] ⚠️ Search indexing appears to be down or degraded. Tests may experience search-related failures.'
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkFileIndexing(uniqueId) {
|
||||
const testFileName = `${TEST_PREFIX}-file-${uniqueId}.txt`;
|
||||
let createdNodeId;
|
||||
let indexed = false;
|
||||
|
||||
try {
|
||||
console.info(`[ 🔍 File ] Creating test file: ${testFileName}`);
|
||||
const createdNode = await nodesApi.createNode('-my-', {
|
||||
name: testFileName,
|
||||
nodeType: 'cm:content'
|
||||
});
|
||||
createdNodeId = createdNode.entry.id;
|
||||
console.info(`[ 🔍 File ] File created with id: ${createdNodeId}`);
|
||||
|
||||
indexed = await waitForIndexing('File', testFileName, async () => {
|
||||
const result = await searchApi.search({
|
||||
query: { query: `cm:name:"${testFileName}"`, language: 'afts' },
|
||||
paging: { skipCount: 0, maxItems: 5 }
|
||||
});
|
||||
const entries = result?.list?.entries || [];
|
||||
return entries.some((entry) => entry.entry?.name === testFileName);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[ 🔍 File ] Error during file indexing check: ${error?.message || error}`);
|
||||
} finally {
|
||||
if (createdNodeId) {
|
||||
try {
|
||||
await nodesApi.deleteNode(createdNodeId, { permanent: true });
|
||||
console.info(`[ 🔍 File ] Test file "${testFileName}" deleted successfully.`);
|
||||
} catch (error) {
|
||||
console.warn(`[ 🔍 File ] Failed to delete test file "${testFileName}": ${error?.message || error}`);
|
||||
}
|
||||
} else {
|
||||
console.warn(`[ 🔍 File ] No node ID found for "${testFileName}", skipping deletion.`);
|
||||
}
|
||||
}
|
||||
|
||||
return { name: testFileName, indexed };
|
||||
}
|
||||
|
||||
async function waitForIndexing(label, name, searchFn) {
|
||||
const maxRetries = Math.ceil(INDEXING_TIMEOUT_SECONDS / RETRY_INTERVAL_SECONDS);
|
||||
const startTime = Date.now();
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
const found = await searchFn();
|
||||
if (found) {
|
||||
const elapsedSeconds = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
console.info(`[ 🔍 ${label} ] "${name}" indexed after ${elapsedSeconds}s (attempt ${attempt}/${maxRetries}).`);
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[ 🔍 ${label} ] Search query failed on attempt ${attempt}: ${error?.message || error}`);
|
||||
}
|
||||
|
||||
if (attempt % 10 === 0) {
|
||||
const elapsedSeconds = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
console.info(`[ 🔍 ${label} ] Still waiting for "${name}"... ${elapsedSeconds}s elapsed (attempt ${attempt}/${maxRetries}).`);
|
||||
}
|
||||
|
||||
if (attempt < maxRetries) {
|
||||
await delayInSeconds(RETRY_INTERVAL_SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function checkUserIndexing(uniqueId) {
|
||||
const testUsername = `${generateRandomString(5)}-${uniqueId}`;
|
||||
const email = `${generateRandomString(5)}`;
|
||||
const firstName = `${generateRandomString(5)}`;
|
||||
const lastName = `${generateRandomString(5)}`;
|
||||
const password = `${generateRandomString(5)}`;
|
||||
let indexed = false;
|
||||
|
||||
try {
|
||||
console.info(`[ 🔍 User ] Creating test user: ${testUsername}`);
|
||||
await peopleApi.createPerson({
|
||||
id: testUsername,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password
|
||||
});
|
||||
console.info(`[ 🔍 User ] User "${testUsername}" created successfully.`);
|
||||
|
||||
const adminSearchApiUser = new SearchApi(adminAlfrescoJsApi);
|
||||
indexed = await waitForIndexing('User/Group', testUsername, async () => {
|
||||
const result = await adminSearchApiUser.search({
|
||||
query: {
|
||||
query: `(userName:*${testUsername}* OR authorityName:*${testUsername}*) AND PATH:"//cm:APP.DEFAULT/*"`,
|
||||
language: 'afts'
|
||||
},
|
||||
paging: { skipCount: 0, maxItems: 20 },
|
||||
filterQueries: [{ query: "TYPE:'cm:authority'" }]
|
||||
});
|
||||
const entries = result?.list?.entries || [];
|
||||
return entries.length > 0;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[ 🔍 User ] Error during user indexing check: ${error?.message || error}`);
|
||||
}
|
||||
|
||||
return { name: testUsername, indexed };
|
||||
}
|
||||
|
||||
async function checkGroupIndexing(uniqueId) {
|
||||
const testGroupId = `${TEST_PREFIX}-group-${uniqueId}`;
|
||||
const testGroupDisplayName = `SearchCheckGroup ${uniqueId}`;
|
||||
let created = false;
|
||||
let indexed = false;
|
||||
|
||||
try {
|
||||
console.info(`[ 🔍 Group ] Creating test group: ${testGroupDisplayName}`);
|
||||
await groupsApi.createGroup({
|
||||
id: testGroupId,
|
||||
displayName: testGroupDisplayName
|
||||
});
|
||||
created = true;
|
||||
console.info(`[ 🔍 Group ] Group "${testGroupDisplayName}" created successfully.`);
|
||||
|
||||
const adminSearchApiGroup = new SearchApi(adminAlfrescoJsApi);
|
||||
indexed = await waitForIndexing('User/Group', testGroupId, async () => {
|
||||
const result = await adminSearchApiGroup.search({
|
||||
query: {
|
||||
query: `(authorityName:*${testGroupId}* OR authorityDisplayName:*${testGroupId}*) AND PATH:"//cm:APP.DEFAULT/*"`,
|
||||
language: 'afts'
|
||||
},
|
||||
paging: { skipCount: 0, maxItems: 20 },
|
||||
filterQueries: [{ query: "TYPE:'cm:authority'" }]
|
||||
});
|
||||
const entries = result?.list?.entries || [];
|
||||
return entries.length > 0;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[ 🔍 Group ] Error during group indexing check: ${error?.message || error}`);
|
||||
} finally {
|
||||
if (created) {
|
||||
try {
|
||||
await groupsApi.deleteGroup(`GROUP_${testGroupId}`);
|
||||
console.info(`[ 🔍 Group ] Test group "${testGroupDisplayName}" deleted successfully.`);
|
||||
} catch (error) {
|
||||
console.warn(`[ 🔍 Group ] Failed to delete test group "${testGroupDisplayName}": ${error?.message || error}`);
|
||||
}
|
||||
} else {
|
||||
console.warn(`[ 🔍 Group ] Group "${testGroupDisplayName}" was not created, skipping deletion.`);
|
||||
}
|
||||
}
|
||||
|
||||
return { name: testGroupDisplayName, indexed };
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function checkCategoryIndexing(uniqueId) {
|
||||
const testCategoryName = `${TEST_PREFIX}-category-${uniqueId}`;
|
||||
let createdCategoryId;
|
||||
let indexed = false;
|
||||
|
||||
try {
|
||||
console.info(`[ 🔍 Category ] Creating test category: ${testCategoryName}`);
|
||||
createdCategoryId = (await categoriesApi.createSubcategories('-root-', [{ name: testCategoryName }])).entry?.id;
|
||||
console.info(`[ 🔍 Category ] Category "${testCategoryName}" created with id: ${createdCategoryId}`);
|
||||
|
||||
const adminSearchApiCategory = new SearchApi(adminAlfrescoJsApi);
|
||||
indexed = await waitForIndexing('Category', testCategoryName, async () => {
|
||||
const result = await adminSearchApiCategory.search({
|
||||
query: {
|
||||
query: `cm:name:"*${testCategoryName}*" AND TYPE:'cm:category' AND PATH:"/cm:categoryRoot/cm:generalclassifiable//*"`,
|
||||
language: 'afts'
|
||||
},
|
||||
paging: { skipCount: 0, maxItems: 25 }
|
||||
});
|
||||
const entries = result?.list?.entries || [];
|
||||
return entries.some((entry) => entry.entry?.name === testCategoryName);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[ 🔍 Category ] Error during category indexing check: ${error?.message || error}`);
|
||||
} finally {
|
||||
if (createdCategoryId) {
|
||||
try {
|
||||
await categoriesApi.deleteCategory(createdCategoryId);
|
||||
console.info(`[ 🔍 Category ] Test category "${testCategoryName}" deleted successfully.`);
|
||||
} catch (error) {
|
||||
console.warn(`[ 🔍 Category ] Failed to delete test category "${testCategoryName}": ${error?.message || error}`);
|
||||
}
|
||||
} else {
|
||||
console.warn(`[ 🔍 Category ] No category ID found for "${testCategoryName}", skipping deletion.`);
|
||||
}
|
||||
}
|
||||
|
||||
return { name: testCategoryName, indexed };
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function delayInSeconds(seconds) {
|
||||
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('[ 🔍 Search Indexing Check ] Unexpected error:', error);
|
||||
exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user