testing ci for tag changes

This commit is contained in:
SanjoyHyland2025
2026-08-25 13:31:56 +05:30
parent cbabb8162a
commit 538c7db4a7
4 changed files with 9 additions and 400 deletions
@@ -233,30 +233,15 @@ var Filters =
filterParams.query = "+ID:\"" + parsedArgs.nodeRef + "\"";
break;
case "tag":
// Remove any trailing "/" character
if (filterData.charAt(filterData.length - 1) == "/")
{
filterData = filterData.slice(0, -1);
}
filterParams.language = "fts-alfresco";
var encodedTag = search.ISO9075Encode(filterData.toLowerCase());
var tagNodes = search.luceneSearch('+PATH:"/cm:categoryRoot/cm:taggable//cm:' + encodedTag + '"');
if (tagNodes && tagNodes.length > 0)
{
filterParams.query = '+=cm\\:taggable:"' + tagNodes[0].nodeRef.toString() + '"';
}
else
{
// Unknown tag - return no results rather than every document
return {
query: null,
limitResults: 0,
sort: [],
language: "fts-alfresco"
};
}
break;
case "tag":
// Remove any trailing "/" character
if (filterData.charAt(filterData.length - 1) == "/")
{
filterData = filterData.slice(0, -1);
}
filterQuery = this.constructPathQuery(parsedArgs);
filterParams.query = filterQuery + " +TAG:\"" + search.ISO9075Encode(filterData) + "\"";
break;
case "category":
@@ -41,7 +41,6 @@ import org.alfresco.util.testing.category.NonBuildTests;
org.alfresco.repo.wiki.WikiServiceImplTest.class,
org.alfresco.slingshot.documentlibrary.FolderTemplateTest.class,
org.alfresco.slingshot.web.scripts.SlingshotContentGetTest.class,
org.alfresco.slingshot.web.scripts.FiltersLibTest.class,
})
public class ShareServicesTestSuite
{}
@@ -1,209 +0,0 @@
/*
* Copyright 2005 - 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/>.
*/
package org.alfresco.slingshot.web.scripts;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;
/**
* Unit tests for the "tag" filter query building logic in the slingshot doclist v2 web script library ({@code documentlibrary-v2/filters.lib.js}).
* <p>
* The test loads the <em>actual</em> {@code filters.lib.js} resource and executes {@code Filters.getFilterParams("tag", ...)} directly in the embedded Rhino JavaScript engine, injecting lightweight mock collaborators for the {@code args}, {@code search} and {@code logger} root objects. It therefore isolates and asserts the exact query string produced for a tag, with no database, Spring context or search index required.
* <p>
* a tag whose value contains a space (e.g. {@code "long tag"}) must be ISO9075-encoded when locating the tag node, and the resulting query must be a well-formed {@code +=cm:taggable:"<nodeRef>"} membership query rather than a broken multi-word query.
*
* @author GitHub Copilot
*/
public class FiltersLibTest
{
/** Classpath location of the library under test (packaged as a web script resource). */
private static final String FILTERS_LIB = "alfresco/templates/webscripts/org/alfresco/slingshot/documentlibrary-v2/filters.lib.js";
/** NodeRef that the mocked tag lookup ({@code search.luceneSearch}) resolves to. */
private static final String TAG_NODEREF = "workspace://SpacesStore/00000000-0000-0000-0000-000000000001";
/**
* Mock root objects and capture hooks, evaluated before the library so that the JavaScript globals referenced by {@code getFilterParams} (args, search, logger) are available. {@code __lastLuceneQuery} captures the path query passed to the tag lookup so the test can assert the tag value was correctly ISO9075-encoded.
*/
private static final String MOCKS = "var __lastLuceneQuery = null;\n"
+ "var __returnEmpty = false;\n"
+ "var search = {\n"
+ " ISO9075Encode: function(s) { return String(s).replace(/ /g, '_x0020_'); },\n"
+ " luceneSearch: function(q) {\n"
+ " __lastLuceneQuery = q;\n"
+ " return __returnEmpty ? [] : [ { nodeRef: { toString: function() { return '" + TAG_NODEREF + "'; } } } ];\n"
+ " }\n"
+ "};\n"
+ "var logger = {\n"
+ " isDebugLoggingEnabled: function() { return false; }, debug: function() {},\n"
+ " isWarnLoggingEnabled: function() { return false; }, warn: function() {},\n"
+ " isLoggingEnabled: function() { return false; }\n"
+ "};\n";
private Context cx;
private Scriptable scope;
@Before
public void setUp() throws IOException
{
cx = Context.enter();
// Interpretive mode - no bytecode generation required for a simple script evaluation
cx.setOptimizationLevel(-1);
scope = cx.initStandardObjects();
cx.evaluateString(scope, MOCKS, "mocks", 1, null);
cx.evaluateString(scope, readResource(FILTERS_LIB), "filters.lib.js", 1, null);
}
@After
public void tearDown()
{
Context.exit();
}
/**
* Baseline: a single-word tag resolves the tag node and produces a well-formed {@code +=cm:taggable:"<nodeRef>"} membership query using the fts-alfresco language.
*/
@Test
public void tagQueryForSingleWordTag()
{
Scriptable result = runTagFilter("mytag", false);
assertEquals("fts-alfresco", getString(result, "language"));
assertEquals("+=cm\\:taggable:\"" + TAG_NODEREF + "\"", getString(result, "query").trim());
// The tag value is looked up under the category root, ISO9075-encoded
assertEquals("+PATH:\"/cm:categoryRoot/cm:taggable//cm:mytag\"", getLastLuceneQuery());
}
/**
* when a tag containing a space must be ISO9075-encoded (space -> _x0020_) when the tag node is located, and must still yield a valid membership query.
*/
@Test
public void tagQueryForTagWithSpaceIsEncoded()
{
Scriptable result = runTagFilter("long tag", false);
assertEquals("fts-alfresco", getString(result, "language"));
assertEquals("+=cm\\:taggable:\"" + TAG_NODEREF + "\"", getString(result, "query").trim());
// The crux of the fix: the space is ISO9075-encoded rather than breaking the query
assertEquals("+PATH:\"/cm:categoryRoot/cm:taggable//cm:long_x0020_tag\"", getLastLuceneQuery());
}
/**
* The tag value is normalised before lookup: a trailing slash is stripped and the value is lower-cased, so mixed-case / trailing-slash input resolves to the same encoded path.
*/
@Test
public void tagValueIsNormalisedBeforeLookup()
{
runTagFilter("Long Tag/", false);
assertEquals("+PATH:\"/cm:categoryRoot/cm:taggable//cm:long_x0020_tag\"", getLastLuceneQuery());
}
/**
* Safety net: when the tag cannot be resolved to a node, the filter must return a null query (i.e. no results) rather than an unbounded query that would return every document.
*/
@Test
public void unknownTagReturnsNoResults()
{
Scriptable result = runTagFilter("does not exist", true);
assertNull("Unknown tag must produce a null query (no results)", getRaw(result, "query"));
assertEquals("fts-alfresco", getString(result, "language"));
assertEquals(0.0, Context.toNumber(getRaw(result, "limitResults")), 0.0);
}
// ---------------------------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------------------------
/**
* Invokes {@code Filters.getFilterParams("tag", parsedArgs, {})} with the given tag value and lookup behaviour, returning the resulting parameters object.
*
* @param filterData
* the raw tag value supplied as the {@code filterData} argument
* @param returnEmpty
* when {@code true}, the mocked tag lookup returns no matching node
* @return the JavaScript filter parameters object produced by the library
*/
private Scriptable runTagFilter(String filterData, boolean returnEmpty)
{
String setup = "args = { filterData: " + jsString(filterData) + ", sortAsc: null, sortField: null, max: null, days: null };\n"
+ "parsedArgs = { pathNode: { qnamePath: '/app:company_home' }, type: '' };\n"
+ "__returnEmpty = " + returnEmpty + ";\n"
+ "__result = Filters.getFilterParams('tag', parsedArgs, {});\n";
cx.evaluateString(scope, setup, "tag-filter-invocation", 1, null);
Object result = scope.get("__result", scope);
assertNotNull("getFilterParams should return an object", result);
assertTrue("getFilterParams should return a JavaScript object", result instanceof Scriptable);
return (Scriptable) result;
}
private String getLastLuceneQuery()
{
return Context.toString(scope.get("__lastLuceneQuery", scope));
}
private static Object getRaw(Scriptable obj, String property)
{
Object value = ScriptableObject.getProperty(obj, property);
if (value == Scriptable.NOT_FOUND || value instanceof Undefined)
{
return null;
}
return value;
}
private static String getString(Scriptable obj, String property)
{
Object value = getRaw(obj, property);
return value == null ? null : Context.toString(value);
}
/** Renders a Java string as a safe single-quoted JavaScript string literal. */
private static String jsString(String value)
{
return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'";
}
private String readResource(String path) throws IOException
{
try (InputStream is = getClass().getClassLoader().getResourceAsStream(path))
{
assertNotNull("Could not find '" + path + "' on the test classpath", is);
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
}
}
}
@@ -1,166 +0,0 @@
/*
* #%L
* Alfresco Search Services E2E Test
* %%
* Copyright (C) 2005 - 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.rest.search;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.util.List;
import io.restassured.path.json.JsonPath;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.core.RestRequest;
import org.alfresco.utility.Utility;
import org.alfresco.utility.data.RandomData;
import org.alfresco.utility.model.FileModel;
import org.alfresco.utility.model.FileType;
/**
* End-to-end test for the Share Document Library "tag" filter (slingshot {@code doclist} webscript, driven by {@code filters.lib.js}) running against a real search server.
* <p>
* clicking a tag that contains a space used to return either no documents or every document. This test tags documents through the public v1 REST API, waits for the live index to catch up, then calls the same {@code /slingshot/doclib2/doclist} endpoint the Share UI uses and asserts that the tag filter returns exactly the tagged document - for both a single-word tag and a tag containing a space.
* <p>
* The test lives in {@code org.alfresco.rest.search} so it is picked up automatically by the Elasticsearch E2E suite ({@code elasticsearch-e2e-suite.xml}), proving the fix works against an Elasticsearch server.
*/
@SuppressWarnings({"PMD.MethodNamingConventions", "PMD.LongVariable"})
public class DocumentLibraryTagFilterTest extends AbstractE2EFunctionalTest
{
/** Webscript service prefix for the slingshot doclist endpoint (equivalent to {@code /alfresco/s}). */
private static final String DOCLIST_BASE_PATH = "alfresco/service/slingshot/doclib2/doclist";
/** Default Share Document Library container name. */
private static final String DOCUMENT_LIBRARY = "documentLibrary";
private String singleWordTag;
private String spaceTag;
private FileModel singleWordTaggedFile;
private FileModel spaceTaggedFile;
@BeforeClass(alwaysRun = true)
public void dataPreparation()
{
// Unique suffix keeps the tags private to this test run (the tag filter is repo-wide, not site-scoped).
String unique = RandomData.getRandomName("Tag").toLowerCase();
singleWordTag = "single" + unique;
spaceTag = "long " + unique; // contains a space - the scenario that used to fail
singleWordTaggedFile = createTaggedFile(singleWordTag);
spaceTaggedFile = createTaggedFile(spaceTag);
// Wait until both tags resolve through the doclist endpoint (category node + cm:taggable both indexed).
assertTrue(waitForTagFilter(singleWordTag, singleWordTaggedFile.getName()),
"Single-word tag was not indexed/searchable in time: " + singleWordTag);
assertTrue(waitForTagFilter(spaceTag, spaceTaggedFile.getName()),
"Space-containing tag was not indexed/searchable in time: " + spaceTag);
}
/** A tag containing a space must return exactly the document it was applied to. */
@Test
public void tagFilterWithSpaceInTagNameReturnsOnlyTheTaggedDocument()
{
assertTagFilterReturnsExactly(spaceTag, spaceTaggedFile.getName(), singleWordTaggedFile.getName());
}
/** Regression guard: single-word tags keep working exactly as before. */
@Test
public void tagFilterWithSingleWordTagReturnsOnlyTheTaggedDocument()
{
assertTagFilterReturnsExactly(singleWordTag, singleWordTaggedFile.getName(), spaceTaggedFile.getName());
}
// -------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------
/** Creates a text document in the test site's document library and tags it via the public v1 REST API. */
private FileModel createTaggedFile(String tag)
{
FileModel file = FileModel.getRandomFileModel(FileType.TEXT_PLAIN, "MNT-25799 tag filter test content");
dataContent.usingUser(testUser).usingSite(testSite).createContent(file);
restClient.authenticateUser(testUser).withCoreAPI().usingResource(file).addTag(tag);
restClient.assertStatusCodeIs(HttpStatus.CREATED);
return file;
}
/** Runs the tag filter and asserts it returns exactly the expected file and never the other (unrelated) file. */
private void assertTagFilterReturnsExactly(String tag, String expectedFileName, String excludedFileName)
{
JsonPath json = tagFilter(tag);
restClient.assertStatusCodeIs(HttpStatus.OK);
List<String> fileNames = json.getList("items.location.file");
assertNotNull(fileNames, "Doclist response did not contain an items list for tag: " + tag);
assertTrue(fileNames.contains(expectedFileName),
"Tag filter '" + tag + "' did not return the tagged document '" + expectedFileName + "'. Got: " + fileNames);
assertFalse(fileNames.contains(excludedFileName),
"Tag filter '" + tag + "' incorrectly returned an unrelated document '" + excludedFileName + "'. Got: " + fileNames);
assertEquals(json.getInt("totalRecords"), 1,
"Tag filter '" + tag + "' returned an unexpected number of documents. Got: " + fileNames);
}
/** Polls the doclist tag filter until {@code expectedFileName} appears or the retry budget is exhausted. */
private boolean waitForTagFilter(String tag, String expectedFileName)
{
for (int attempt = 0; attempt < SEARCH_MAX_ATTEMPTS; attempt++)
{
JsonPath json = tagFilter(tag);
if (String.valueOf(HttpStatus.OK.value()).equals(restClient.getStatusCode()))
{
List<String> fileNames = json.getList("items.location.file");
if (fileNames != null && fileNames.contains(expectedFileName))
{
return true;
}
}
Utility.waitToLoopTime(properties.getSolrWaitTimeInSeconds(),
"Waiting for tag to be indexed. Attempt: " + (attempt + 1));
}
return false;
}
/**
* Calls the slingshot doclist webscript with the tag filter, as the Share UI does: {@code GET /alfresco/s/slingshot/doclib2/doclist/all/site/{site}/documentLibrary?filter=tag&filterData=<tag>}.
*/
private JsonPath tagFilter(String tag)
{
restClient.authenticateUser(testUser);
restClient.configureRequestSpec().setBasePath(DOCLIST_BASE_PATH);
RestRequest request = RestRequest.simpleRequest(HttpMethod.GET,
"all/site/{site}/{container}?filter=tag&filterData={filterData}",
testSite.getId(), DOCUMENT_LIBRARY, tag);
return restClient.process(request).getResponse().jsonPath();
}
}