diff --git a/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/ACheckerAPI.java b/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/ACheckerAPI.java new file mode 100644 index 000000000000..66294d0a03db --- /dev/null +++ b/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/ACheckerAPI.java @@ -0,0 +1,54 @@ +package com.dotcms.enterprise.achecker; + +import com.dotcms.enterprise.achecker.model.GuideLineBean; +import com.dotmarketing.business.DotValidationException; +import com.dotmarketing.exception.DotDataException; + +import java.util.List; +import java.util.Map; + +/** + * This API provides the methods to interact with the Accessibility Checker service in dotCMS. Users + * of this API can access the different Accessibility Guidelines available in the system, and + * validate content against them. + * + * @author Jose Castro + * @since Sep 5th, 2024 + */ +public interface ACheckerAPI { + + String LANG = "lang"; + String CONTENT = "content"; + String GUIDELINES = "guidelines"; + String FRAGMENT = "fragment"; + + /** + * Returns the list of Accessibility Guidelines available in the system. + * + * @return The list of Accessibility Guidelines available in the system. + * + * @throws DotDataException If the guidelines can't be retrieved. + */ + List getAccessibilityGuidelineList() throws DotDataException; + + /** + * Validates the given content against the specified Accessibility Guidelines. + * + * @param validationData Map containing the parameters to validate: + * + * + * @return The result of the validation, as a JSON object. + * + * @throws DotValidationException If the validation fails. + */ + ACheckerResponse validate(final Map validationData) throws DotValidationException; + +} diff --git a/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/impl/ACheckerAPIImpl.java b/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/impl/ACheckerAPIImpl.java new file mode 100644 index 000000000000..e7bc315b3d52 --- /dev/null +++ b/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/impl/ACheckerAPIImpl.java @@ -0,0 +1,83 @@ +package com.dotcms.enterprise.achecker.impl; + +import com.dotcms.enterprise.achecker.ACheckerAPI; +import com.dotcms.enterprise.achecker.ACheckerRequest; +import com.dotcms.enterprise.achecker.ACheckerResponse; +import com.dotcms.enterprise.achecker.dao.GuidelinesDAO; +import com.dotcms.enterprise.achecker.model.GuideLineBean; +import com.dotcms.enterprise.achecker.tinymce.DaoLocator; +import com.dotcms.exception.ExceptionUtil; +import com.dotcms.util.EnterpriseFeature; +import com.dotmarketing.business.DotValidationException; +import com.dotmarketing.exception.DotDataException; +import com.dotmarketing.util.UtilMethods; + +import java.util.List; +import java.util.Map; + +/** + * Implements the {@link ACheckerAPI} interface. + * + * @author Jose Castro + * @since Sep 5th, 2024 + */ +public class ACheckerAPIImpl implements ACheckerAPI { + + private List accessibilityGuidelineList = null; + + /** + * Returns the list of Accessibility Guidelines available in the system. + * + * @return The list of Accessibility Guidelines available in the system. + * + * @throws Exception If the guidelines can't be retrieved. + */ + @EnterpriseFeature + public List getAccessibilityGuidelineList() throws DotDataException { + try { + if (UtilMethods.isNotSet(this.accessibilityGuidelineList)) { + GuidelinesDAO gLines = DaoLocator.getGuidelinesDAO(); + this.accessibilityGuidelineList = gLines.getOpenGuidelines(); + } + return this.accessibilityGuidelineList; + } catch (final Exception e) { + throw new DotDataException(ExceptionUtil.getErrorMessage(e), e); + } + } + + /** + * Validates the given content against the specified Accessibility Guidelines. + * + * @param validationData Map containing the parameters to validate: + *
    + *
  • {@code lang}: the language of the content to validate (ISO + * 639-1, 2 letters)
  • + *
  • {@code content}: the content to validate
  • + *
  • {@code guidelines}: the guidelines to validate against + * (comma-separated list of guideline abbreviations)
  • + *
  • {@code fragment}: whether the content is a fragment (does + * not contain the required HTML tags)
  • + *
+ * + * @return The result of the validation, as a JSON object. + * + * @throws DotValidationException If the validation fails. + */ + @EnterpriseFeature + public ACheckerResponse validate(final Map validationData) throws DotValidationException { + final String lang = validationData.get(LANG); + final String content = validationData.get(CONTENT); + final String guidelines = validationData.get(GUIDELINES); + final String fragment = validationData.get(FRAGMENT); + try { + if (UtilMethods.isSet(lang) && lang.trim().length() == 2) { + DaoLocator.getLangCodesDAO().getLangCodeBy3LetterCode(lang); + } + final ACheckerRequest request = new ACheckerRequest(lang, content, guidelines, Boolean.parseBoolean(fragment)); + return new ACheckerImpl().validate(request); + } catch (final Exception e) { + throw new DotValidationException(ExceptionUtil.getErrorMessage(e), e); + } + } + +} diff --git a/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/model/GuideLineBean.java b/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/model/GuideLineBean.java index 4712ff3eb4c8..a7ae7319e2e3 100644 --- a/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/model/GuideLineBean.java +++ b/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/model/GuideLineBean.java @@ -45,16 +45,21 @@ package com.dotcms.enterprise.achecker.model; +import com.dotcms.enterprise.achecker.utility.Constants; import java.beans.IntrospectionException; import java.lang.reflect.InvocationTargetException; import java.util.Map; -import com.dotcms.enterprise.achecker.utility.Constants; -import com.dotcms.enterprise.achecker.model.ReflectionBean; - +/** + * This class represents a Validation Guideline use by the Accessibility Checker to determine + * whether a specified content meets the expected requirements. Content Authors can select the + * guideline they want to validate against a given content. + * + * @author root + * @since N/A + */ public class GuideLineBean extends ReflectionBean { - private String preamble; private String earlid; @@ -167,5 +172,22 @@ public boolean isDefaultGuideLine() { return Constants.DEFAULT_GUIDELINE.equalsIgnoreCase(this.abbr); } - + @Override + public String toString() { + return "GuideLineBean{" + + "preamble='" + preamble + '\'' + + ", earlid='" + earlid + '\'' + + ", long_name='" + long_name + '\'' + + ", abbr='" + abbr + '\'' + + ", title='" + title + '\'' + + ", guideline_id=" + guideline_id + + ", user_id=" + user_id + + ", status=" + status + + ", open_to_public=" + open_to_public + + ", seal_icon_name='" + seal_icon_name + '\'' + + ", subset='" + subset + '\'' + + ", defaultGuideLine=" + defaultGuideLine + + '}'; + } + } diff --git a/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/tinymce/ACheckerDWR.java b/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/tinymce/ACheckerDWR.java index a341a9e67aa2..81af21890888 100644 --- a/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/tinymce/ACheckerDWR.java +++ b/dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/tinymce/ACheckerDWR.java @@ -45,75 +45,53 @@ package com.dotcms.enterprise.achecker.tinymce; -import java.util.List; -import java.util.Map; - import com.dotcms.enterprise.LicenseUtil; -import com.dotcms.enterprise.achecker.ACheckerRequest; import com.dotcms.enterprise.achecker.ACheckerResponse; -import com.dotcms.enterprise.achecker.dao.GuidelinesDAO; -import com.dotcms.enterprise.achecker.dao.LangCodesDAO; -import com.dotcms.enterprise.achecker.impl.ACheckerImpl; import com.dotcms.enterprise.achecker.model.GuideLineBean; -import com.dotcms.enterprise.achecker.tinymce.DaoLocator; import com.dotcms.enterprise.license.LicenseLevel; +import com.dotcms.exception.ExceptionUtil; +import com.dotcms.rest.api.v1.accessibility.ACheckerResource; +import com.dotmarketing.business.APILocator; +import com.dotmarketing.util.Logger; +import java.util.List; +import java.util.Map; +/** + * @deprecated This one and all DWR-related classes will be deprecated in the near future. Please + * use the REST {@link ACheckerResource} Endpoint instead. + */ +@Deprecated(forRemoval = true) public class ACheckerDWR { - private List listaGuidelines = null; - private List getListaGudelines() throws Exception { - if(LicenseUtil.getLevel()< LicenseLevel.STANDARD.level) - throw new RuntimeException("need enterprise license"); - try{ - if( listaGuidelines == null ){ - GuidelinesDAO gLines = DaoLocator.getGuidelinesDAO(); - listaGuidelines = gLines.getOpenGuidelines(); - } - return listaGuidelines; - }catch (Exception e) { - throw e; - } + return APILocator.getACheckerAPI().getAccessibilityGuidelineList(); } - public List getSupportedGudelines(){ - if(LicenseUtil.getLevel() params) { - if(LicenseUtil.getLevel() params) { + if (LicenseUtil.getLevel() guidelines = APILocator.getACheckerAPI().getAccessibilityGuidelineList(); + return Response.ok(new ResponseEntityView<>(guidelines)).build(); + } + + /** + * Validates the given content against the given guidelines. This endpoint is only available to + * users with a valid Enterprise license. The request body must contain the following + * parameters: + *
    + *
  • {@code content}: the content to validate (required)
  • + *
  • {@code guidelines}: the guidelines to validate against (required)
  • + *
+ *

The response will contain the validation result as a + * {@link ACheckerResponse} object.

+ * + * @param request the HTTP request + * @param response the HTTP response + * @param accessibilityForm the request body object containing the content and guidelines to + * validate + * + * @return the {@link ACheckerResponse} object with the list of issues found during the + * validation, if any. + */ + @POST + @Path("/_validate") + @JSONP + @NoCache + @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) + @Operation( + operationId = "postValidateContent", + summary = "Validates content", + description = "Validates the given content against the one or more Accessibility Guidelines.", + tags = {"Accessibility Checker"}, + responses = { + @ApiResponse(responseCode = "200", description = "Content validated successfully", + content = @Content(mediaType = "application/json", + examples = { + @ExampleObject( + value = "{\n" + + " \"entity\": {\n" + + " \"errors\": [\n" + + " {\n" + + " \"check\": {\n" + + " \"check_id\": 98,\n" + + " \"confidence\": 2,\n" + + " \"confidenceEnum\": \"POTENTIAL\",\n" + + " \"decision_fail\": \"Not all abbreviations are marked with the abbr element.\",\n" + + " \"decision_pass\": \"All abbreviations are marked with the abbr element.\",\n" + + " \"description\": \"If body element content is greater than 10 characters (English) this error will be generated.\",\n" + + " \"err\": \"Abbreviations may not be marked with abbr element.\",\n" + + " \"func\": \"return (BasicFunctions::getPlainTextLength() <= 10);\",\n" + + " \"how_to_repair\": \"\",\n" + + " \"html_tag\": \"body\",\n" + + " \"lang\": \"en\",\n" + + " \"long_description\": null,\n" + + " \"name\": \"Abbreviations must be marked with abbr element.\",\n" + + " \"note\": \"\",\n" + + " \"open_to_public\": 1,\n" + + " \"question\": \"Are there any abbreviations in the document that are not marked with the abbr element?\",\n" + + " \"rationale\": \"_RATIONALE_98\",\n" + + " \"repair_example\": \"\",\n" + + " \"search_str\": null,\n" + + " \"test_expected_result\": \"1. All abbreviations are expected to be marked with a valid abbr element.\",\n" + + " \"test_failed_result\": \"1. Mark all abbreviations with a valid abbr element.\",\n" + + " \"test_procedure\": \"1. Check the document for any text abbreviations.\\\\n2. If an abbreviation is found, check if it is properly marked with the abbr element.\",\n" + + " \"user_id\": 0\n" + + " },\n" + + " \"col_number\": 110,\n" + + " \"cssCode\": null,\n" + + " \"htmlCode\": \" Adventure travel done right Wherever you want to go, whatever you want to get into, we’ve go...\",\n" + + " \"image\": null,\n" + + " \"imageAlt\": null,\n" + + " \"line_number\": 0,\n" + + " \"success\": false\n" + + " },\n" + + " ...\n" + + " ...\n" + + " ],\n" + + " \"lang\": \"en\",\n" + + " \"results\": [\n" + + " {\n" + + " \"check\": {\n" + + " \"check_id\": 29,\n" + + " \"confidence\": 0,\n" + + " \"confidenceEnum\": \"KNOWN\",\n" + + " \"decision_fail\": \"\",\n" + + " \"decision_pass\": \"\",\n" + + " \"description\": \"Each document must contain a valid doctype declaration.\",\n" + + " \"err\": \"doctype declaration missing.\",\n" + + " \"func\": \"return (BasicFunctions::getNumOfTagInWholeContent(\\\\\\\"doctype\\\\\\\") > 0);\",\n" + + " \"how_to_repair\": \"Add a valid doctype declaration to the document.\",\n" + + " \"html_tag\": \"html\",\n" + + " \"lang\": \"en\",\n" + + " \"long_description\": null,\n" + + " \"name\": \"HTML content has a valid doctype declaration.\",\n" + + " \"note\": \"\",\n" + + " \"open_to_public\": 1,\n" + + " \"question\": \"\",\n" + + " \"rationale\": \"\",\n" + + " \"repair_example\": \"\",\n" + + " \"search_str\": null,\n" + + " \"test_expected_result\": \"1. HTML content has a valid doctype declaration.\",\n" + + " \"test_failed_result\": \"1. Add a valid doctype declaration to the document.\",\n" + + " \"test_procedure\": \"1. Check for the presence of a doctype declaration at the start of the document.\\\\n2. Check the content of the doctype declaration.\",\n" + + " \"user_id\": 0\n" + + " },\n" + + " \"col_number\": 110,\n" + + " \"cssCode\": null,\n" + + " \"htmlCode\": \" Adventure travel done right Wherever you want to go, what...\",\n" + + " \"image\": null,\n" + + " \"imageAlt\": null,\n" + + " \"line_number\": 0,\n" + + " \"success\": true\n" + + " },\n" + + " ...\n" + + " ...\n" + + " ]\n" + + " },\n" + + " \"errors\": [],\n" + + " \"i18nMessagesMap\": {},\n" + + " \"messages\": [],\n" + + " \"pagination\": null,\n" + + " \"permissions\": []\n" + + "}" + ) + } + ) + ), + @ApiResponse(responseCode = "400", description = "Bad Request"), + @ApiResponse(responseCode = "500", description = "Internal Server Error") + } + ) + public final Response validate(@Context final HttpServletRequest request, + @Context final HttpServletResponse response, + @NotNull final AccessibilityForm accessibilityForm) throws Exception { + new WebResource.InitBuilder(this.webResource) + .requestAndResponse(request, response) + .rejectWhenNoUser(true) + .requiredBackendUser(true) + .requireLicense(true) + .init(); + checkNotEmpty(accessibilityForm.get(CONTENT), IllegalArgumentException.class, + String.format("'%s' parameter cannot be empty", CONTENT)); + checkNotEmpty(accessibilityForm.get(GUIDELINES), IllegalArgumentException.class, + String.format("'%s' parameter cannot be empty", GUIDELINES)); + final ACheckerResponse aCheckerResponse = APILocator.getACheckerAPI().validate(accessibilityForm); + return Response.ok(new ResponseACheckerEntityView(aCheckerResponse)).build(); + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/accessibility/AccessibilityForm.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/accessibility/AccessibilityForm.java new file mode 100644 index 000000000000..56eff5c1ee58 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/accessibility/AccessibilityForm.java @@ -0,0 +1,18 @@ +package com.dotcms.rest.api.v1.accessibility; + +import java.util.HashMap; + +/** + * Contains all the different properties used by the Accessibility Checker service to determine + * whether a content meets the expected criteria or not. + * + * @author Jose Castro + * @since Sep 3rd, 2024 + */ +public class AccessibilityForm extends HashMap { + + public AccessibilityForm() { + super(); + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/accessibility/ResponseACheckerEntityView.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/accessibility/ResponseACheckerEntityView.java new file mode 100644 index 000000000000..e6975b9478a4 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/accessibility/ResponseACheckerEntityView.java @@ -0,0 +1,19 @@ +package com.dotcms.rest.api.v1.accessibility; + +import com.dotcms.enterprise.achecker.ACheckerResponse; +import com.dotcms.rest.ResponseEntityView; + +/** + * This Entity View is used to return the response of the Accessibility Checker service indicating + * what issues were found in the specified content. + * + * @author Jose Castro + * @since Sep 5th, 2024 + */ +public class ResponseACheckerEntityView extends ResponseEntityView { + + public ResponseACheckerEntityView(final ACheckerResponse entity) { + super(entity); + } + +} diff --git a/dotCMS/src/main/java/com/dotmarketing/business/APILocator.java b/dotCMS/src/main/java/com/dotmarketing/business/APILocator.java index 6f241d43ecae..345472c99be1 100644 --- a/dotCMS/src/main/java/com/dotmarketing/business/APILocator.java +++ b/dotCMS/src/main/java/com/dotmarketing/business/APILocator.java @@ -47,6 +47,8 @@ import com.dotcms.enterprise.ESSeachAPI; import com.dotcms.enterprise.RulesAPIProxy; import com.dotcms.enterprise.ServerActionAPIImplProxy; +import com.dotcms.enterprise.achecker.ACheckerAPI; +import com.dotcms.enterprise.achecker.impl.ACheckerAPIImpl; import com.dotcms.enterprise.cache.provider.CacheProviderAPI; import com.dotcms.enterprise.cache.provider.CacheProviderAPIImpl; import com.dotcms.enterprise.cluster.action.business.ServerActionAPI; @@ -1161,6 +1163,10 @@ public static SystemAPI getSystemAPI() { return (SystemAPI) getInstance(APIIndex.SYSTEM_API); } + public static ACheckerAPI getACheckerAPI() { + return (ACheckerAPI) getInstance(APIIndex.ACHECKER_API); + } + /** * Generates a unique instance of the specified dotCMS API. * @@ -1315,8 +1321,8 @@ enum APIIndex BAYESIAN_API, ANALYTICS_API, CONTENT_TYPE_DESTROY_API, - - SYSTEM_API; + SYSTEM_API, + ACHECKER_API; Object create() { switch(this) { @@ -1408,6 +1414,7 @@ Object create() { case CONTENT_TYPE_DESTROY_API: return new ContentTypeDestroyAPIImpl(); case SYSTEM_API: return new SystemAPIImpl(); case ARTIFICIAL_INTELLIGENCE_API: return new DotAIAPIFacadeImpl(); + case ACHECKER_API: return new ACheckerAPIImpl(); } throw new AssertionError("Unknown API index: " + this); } diff --git a/dotcms-postman/src/main/resources/postman/Accessibility_Checker_Tests.postman_collection.json b/dotcms-postman/src/main/resources/postman/Accessibility_Checker_Tests.postman_collection.json new file mode 100644 index 000000000000..4da8338726b3 --- /dev/null +++ b/dotcms-postman/src/main/resources/postman/Accessibility_Checker_Tests.postman_collection.json @@ -0,0 +1,674 @@ +{ + "info": { + "_postman_id": "cb25dbbd-2dfc-4105-9c09-a0a55c58927f", + "name": "Accessibility Checker Tests", + "description": "This Postman Collection verifies that the Accessibility Checker -- a.k.a. AChecker -- is working as expected after being migrated from a DWR class to a REST Endpoint. Basically, this new endpoint exposes two methods:\n\n1. Retrieving the list of Accessiblity Guidelines that Content Authors can use to validate content.\n \n2. Send the validation request to the specific service, and receive the list of errors/suggestions that must be followed in order to make the content comply with the selected Accessibility Guideline(s).", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "5403727" + }, + "item": [ + { + "name": "Retrieval And Validation", + "item": [ + { + "name": "Successful Requests", + "item": [ + { + "name": "Get Accessibility Guidelines", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Returned data must be the expected one\", function () {", + " var jsonData = pm.response.json();", + " var entity = pm.response.json().entity;", + " const expectedGuidelines = [\"BITV1\", \"508\", \"STANCA\", \"WCAG1-A\", \"WCAG1-AA\", \"WCAG1-AAA\", \"WCAG2-A\", \"WCAG2-AA\", \"WCAG2-AAA\"];", + " const expectedCount = expectedGuidelines.length;", + " var counter = 0;", + "", + " pm.expect(jsonData.errors.length).to.eql(0, \"An error occurred when retrieving Accessibility Guidelines\");", + " pm.expect(entity.length).to.eql(expectedCount, \"Expected to retrieve \" + expectedCount + \" Accessibility Guidelines\");", + "", + " for (let guideline of entity) {", + " if (expectedGuidelines.find((element) => element == guideline.abbr)) {", + " counter++;", + " }", + " }", + "", + " pm.expect(counter).to.eql(expectedCount, \"Retrieved data doesn't include all the expected Accessibility Guideline abbreviations\");", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/achecker/guidelines", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "achecker", + "guidelines" + ] + }, + "description": "Returns the list of available Accessibility Guidelines for content validation." + }, + "response": [] + }, + { + "name": "Validate Content Against a Guideline", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Returned data must be the expected one\", function () {", + " var jsonData = pm.response.json();", + " var entity = pm.response.json().entity;", + " const expectedErrors = 3;", + "", + " pm.expect(jsonData.errors.length).to.eql(0, \"An error occurred when retrieving validation results from the specified Accessibility Guideline\");", + " pm.expect(entity.errors.length).to.eql(expectedErrors, \"Expected to retrieve \" + expectedErrors + \" validation errors for the specified Accessibility Guideline\");", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "pm.collectionVariables.set(\"guidelines_abbr\", \"BITV1\");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"lang\": \"en\",\n \"content\": \"Adventure travel done right\\n\\nWherever you want to go, whatever you want to get into, we’ve got a trip that’ll make your dream vacation come true. Visit like a local, explore at your own pace, and eat like a king (or a vegan king, if that’s more your thing).\",\n \"guidelines\": \"{{guidelines_abbr}}\",\n \"fragment\": \"true\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/achecker/_validate", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "achecker", + "_validate" + ] + }, + "description": "Validates content against a single Accessibility Guideline." + }, + "response": [] + }, + { + "name": "Validate Content Against Several Guidelines", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Returned data must be the expected one\", function () {", + " var jsonData = pm.response.json();", + " var entity = pm.response.json().entity;", + " const expectedErrors = 4;", + "", + " pm.expect(jsonData.errors.length).to.eql(0, \"An error occurred when retrieving validation results from the specified Accessibility Guideline\");", + " pm.expect(entity.errors.length).to.eql(expectedErrors, \"Expected to retrieve \" + expectedErrors + \" validation errors for the specified Accessibility Guideline\");", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "pm.collectionVariables.set(\"guidelines_abbr\", \"BITV1,WCAG1-AAA\");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"lang\": \"en\",\n \"content\": \"Adventure travel done right\\n\\nWherever you want to go, whatever you want to get into, we’ve got a trip that’ll make your dream vacation come true. Visit like a local, explore at your own pace, and eat like a king (or a vegan king, if that’s more your thing).\",\n \"guidelines\": \"{{guidelines_abbr}}\",\n \"fragment\": \"true\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/achecker/_validate", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "achecker", + "_validate" + ] + }, + "description": "Validates content against a more than one Accessibility Guideline. You can specify more as comma-separated values." + }, + "response": [] + }, + { + "name": "Non-Specified Language", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Returned data must be the expected one\", function () {", + " var jsonData = pm.response.json();", + " var entity = pm.response.json().entity;", + " const expectedErrors = 3;", + "", + " pm.expect(jsonData.errors.length).to.eql(0, \"An error occurred when retrieving validation results from the specified Accessibility Guideline\");", + " pm.expect(entity.errors.length).to.eql(expectedErrors, \"Expected to retrieve \" + expectedErrors + \" validation errors for the specified Accessibility Guideline\");", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "pm.collectionVariables.set(\"guidelines_abbr\", \"BITV1\");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"content\": \"Adventure travel done right\\n\\nWherever you want to go, whatever you want to get into, we’ve got a trip that’ll make your dream vacation come true. Visit like a local, explore at your own pace, and eat like a king (or a vegan king, if that’s more your thing).\",\n \"guidelines\": \"{{guidelines_abbr}}\",\n \"fragment\": \"true\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/achecker/_validate", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "achecker", + "_validate" + ] + }, + "description": "Validate content without specifying a language. This won't affect how the guideline validation works. It's just for dotCMS purposes." + }, + "response": [] + }, + { + "name": "Non-Specified Fragment", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Returned data must be the expected one\", function () {", + " var jsonData = pm.response.json();", + " var entity = pm.response.json().entity;", + " const expectedErrors = 5;", + "", + " pm.expect(jsonData.errors.length).to.eql(0, \"An error occurred when retrieving validation results from the specified Accessibility Guideline\");", + " pm.expect(entity.errors.length).to.eql(expectedErrors, \"Expected to retrieve \" + expectedErrors + \" validation errors for the specified Accessibility Guideline\");", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "pm.collectionVariables.set(\"guidelines_abbr\", \"BITV1\");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"lang\": \"en\",\n \"content\": \"Adventure travel done right\\n\\nWherever you want to go, whatever you want to get into, we’ve got a trip that’ll make your dream vacation come true. Visit like a local, explore at your own pace, and eat like a king (or a vegan king, if that’s more your thing).\",\n \"guidelines\": \"{{guidelines_abbr}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/achecker/_validate", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "achecker", + "_validate" + ] + }, + "description": "Validate a content with the `fragment` attribute set to `false`. This means that our API will NOT automatically include the required HTML code to the content for the Accessibliity Guideline service to validate it correctly. Therefore, more errors will be present." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "pm.test(\"Guideline retrieval must be successful\", function() {", + " pm.response.to.have.status(200);", + "});", + "" + ] + } + } + ] + }, + { + "name": "Failure Requests", + "item": [ + { + "name": "Non-Specified Content", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Returned error message must be the expected one\", function () {", + " var jsonData = pm.response.json();", + "", + " pm.expect(jsonData.message).to.eql(\"'content' parameter cannot be empty\", \"Error message is NOT the expected one\");", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "pm.collectionVariables.set(\"guidelines_abbr\", \"BITV1\");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"lang\": \"en\",\n \"guidelines\": \"{{guidelines_abbr}}\",\n \"fragment\": \"true\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/achecker/_validate", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "achecker", + "_validate" + ] + }, + "description": "The `content` attribute is required. This request will fail." + }, + "response": [] + }, + { + "name": "Non-Specified Guidelines", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Returned error message must be the expected one\", function () {", + " var jsonData = pm.response.json();", + "", + " pm.expect(jsonData.message).to.eql(\"'guidelines' parameter cannot be empty\", \"Error message is NOT the expected one\");", + "});", + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "pm.collectionVariables.set(\"guidelines_abbr\", \"BITV1\");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"lang\": \"en\",\n \"content\": \"Adventure travel done right\\n\\nWherever you want to go, whatever you want to get into, we’ve got a trip that’ll make your dream vacation come true. Visit like a local, explore at your own pace, and eat like a king (or a vegan king, if that’s more your thing).\",\n \"fragment\": \"true\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/achecker/_validate", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "achecker", + "_validate" + ] + }, + "description": "The `guidelines` attribute is required. This request will fail." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "pm.test(\"Guideline retrieval must be Bad Request\", function() {", + " pm.response.to.have.status(400);", + "});", + "" + ] + } + } + ] + }, + { + "name": "Authentication Required", + "item": [ + { + "name": "Get Accessibility Guidelines", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{serverURL}}/api/v1/achecker/guidelines", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "achecker", + "guidelines" + ] + }, + "description": "Returns the list of available Accessibility Guidelines for content validation." + }, + "response": [] + }, + { + "name": "Validate Content Against a Guideline", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript", + "packages": {} + } + }, + { + "listen": "prerequest", + "script": { + "exec": [ + "pm.collectionVariables.set(\"guidelines_abbr\", \"BITV1\");" + ], + "type": "text/javascript", + "packages": {} + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"lang\": \"en\",\n \"content\": \"Adventure travel done right\\n\\nWherever you want to go, whatever you want to get into, we’ve got a trip that’ll make your dream vacation come true. Visit like a local, explore at your own pace, and eat like a king (or a vegan king, if that’s more your thing).\",\n \"guidelines\": \"{{guidelines_abbr}}\",\n \"fragment\": \"true\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{serverURL}}/api/v1/achecker/_validate", + "host": [ + "{{serverURL}}" + ], + "path": [ + "api", + "v1", + "achecker", + "_validate" + ] + }, + "description": "Validates content against a single Accessibility Guideline." + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "pm.test(\"Guideline retrieval must be Unauthorized\", function() {", + " pm.response.to.have.status(401);", + "});", + "" + ] + } + } + ] + } + ] + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{jwt}}", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "if (!pm.environment.get('jwt')) {", + " console.log(\"generating....\")", + " const serverURL = pm.environment.get('serverURL'); // Get the server URL from the environment variable", + " const apiUrl = `${serverURL}/api/v1/apitoken`; // Construct the full API URL", + "", + " if (!pm.environment.get('jwt')) {", + " const username = pm.environment.get(\"user\");", + " const password = pm.environment.get(\"password\");", + " const basicAuth = Buffer.from(`${username}:${password}`).toString('base64');", + "", + " const requestOptions = {", + " url: apiUrl,", + " method: \"POST\",", + " header: {", + " \"accept\": \"*/*\",", + " \"content-type\": \"application/json\",", + " \"Authorization\": `Basic ${basicAuth}`", + " },", + " body: {", + " mode: \"raw\",", + " raw: JSON.stringify({", + " \"expirationSeconds\": 7200,", + " \"userId\": \"dotcms.org.1\",", + " \"network\": \"0.0.0.0/0\",", + " \"claims\": {\"label\": \"postman-tests\"}", + " })", + " }", + " };", + "", + " pm.sendRequest(requestOptions, function (err, response) {", + " if (err) {", + " console.log(err);", + " } else {", + " const jwt = response.json().entity.jwt;", + " pm.environment.set('jwt', jwt);", + " console.log(jwt);", + " }", + " });", + " }", + "}", + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "exec": [ + "" + ] + } + } + ] +} \ No newline at end of file