diff --git a/jahia-test-module/settings/configurations/org.jahia.modules.jahiacsrfguard-jsmtest.cfg b/jahia-test-module/settings/configurations/org.jahia.modules.jahiacsrfguard-jsmtest.cfg index 9997cac4..ca34cca3 100644 --- a/jahia-test-module/settings/configurations/org.jahia.modules.jahiacsrfguard-jsmtest.cfg +++ b/jahia-test-module/settings/configurations/org.jahia.modules.jahiacsrfguard-jsmtest.cfg @@ -1,3 +1,5 @@ # Whitelists the POST test action in Jahia's CSRF guard, following the documented recipe # for JS modules exposing actions to unsafe HTTP methods (see docs/2-guides/4-actions). -whitelist = *.testJsActionPost.do +# testJsActionAuth is a GET, but the guard also challenges authenticated GET .do requests, +# which is how the e2e suite calls it. +whitelist = *.testJsActionPost.do,*.testJsActionAuth.do diff --git a/jahia-test-module/src/react/server/extensions/actions.ts b/jahia-test-module/src/react/server/extensions/actions.ts index 475ddb72..df6742b3 100644 --- a/jahia-test-module/src/react/server/extensions/actions.ts +++ b/jahia-test-module/src/react/server/extensions/actions.ts @@ -37,8 +37,7 @@ registerNodeLegacyAction( registerNodeLegacyAction( { name: "testJsActionRedirect", requiredMethods: ["GET"], requireAuthenticatedUser: false }, - () => ({ - statusCode: 302, - redirect: "/redirected-target", - }), + // No statusCode: the platform picks the redirect status itself (303 unless the request asks for + // another one). Returning a 3xx here would make Jahia answer sendError() instead of redirecting. + () => ({ redirect: "/redirected-target" }), ); diff --git a/javascript-modules-engine/pom.xml b/javascript-modules-engine/pom.xml index 39df7ff3..adb92df9 100644 --- a/javascript-modules-engine/pom.xml +++ b/javascript-modules-engine/pom.xml @@ -81,8 +81,11 @@ <_dsannotations>* - - bndlib,chromeinspector,commons-pool2,pax-swissbox-bnd,graal-sdk,truffle-api,js,icu4j,regex + + bndlib,chromeinspector,profiler,commons-pool2,pax-swissbox-bnd,graal-sdk,truffle-api,js,icu4j,regex diff --git a/tests/cypress/e2e/ui/actionTest.cy.ts b/tests/cypress/e2e/ui/actionTest.cy.ts index fecde858..6bab5c86 100644 --- a/tests/cypress/e2e/ui/actionTest.cy.ts +++ b/tests/cypress/e2e/ui/actionTest.cy.ts @@ -7,6 +7,13 @@ const nodePath = `/sites/${GENERIC_SITE_KEY}/home/${pageName}/pagecontent/test`; const actionUrl = (action: string, workspace = "live") => `/cms/render/${workspace}/en${nodePath}.${action}.do`; +/** + * Jahia only writes an action's JSON body when the caller asks for it (an `accept` header holding + * `application/json`, or a `returnContentType=json` parameter) — legacy node actions declared in JS + * follow the same contract as Java ones, so a JSON-reading caller has to say so. + */ +const JSON_HEADERS = { accept: "application/json" }; + describe("JS actions", () => { before("Create and publish test content", () => { cy.login(); @@ -27,7 +34,10 @@ describe("JS actions", () => { }); it("executes a GET action and returns JSON", () => { - cy.request(`${actionUrl("testJsActionGet")}?echo=hello`).then((response) => { + cy.request({ + url: `${actionUrl("testJsActionGet")}?echo=hello`, + headers: JSON_HEADERS, + }).then((response) => { expect(response.status).to.equal(200); expect(response.body.echo).to.equal("hello"); expect(response.body.path).to.equal(nodePath); @@ -38,6 +48,7 @@ describe("JS actions", () => { cy.request({ method: "POST", url: actionUrl("testJsActionPost"), + headers: JSON_HEADERS, form: true, body: { payload: "some-content" }, }).then((response) => { @@ -54,17 +65,20 @@ describe("JS actions", () => { it("executes an authenticated action for a logged-in user", () => { cy.login(); - cy.request(actionUrl("testJsActionAuth", "default")).then((response) => { - expect(response.status).to.equal(200); - expect(response.body.user).to.equal("root"); - }); + cy.request({ url: actionUrl("testJsActionAuth", "default"), headers: JSON_HEADERS }).then( + (response) => { + expect(response.status).to.equal(200); + expect(response.body.user).to.equal("root"); + }, + ); cy.logout(); }); it("sends redirects", () => { cy.request({ url: actionUrl("testJsActionRedirect"), followRedirect: false }).then( (response) => { - expect(response.status).to.equal(302); + // Jahia chooses the redirect status (303 by default), the action only chooses the target + expect(response.status).to.be.within(300, 308); expect(response.headers.location).to.contain("/redirected-target"); }, ); @@ -87,7 +101,10 @@ describe("JS actions", () => { }, }); cy.logout(); - cy.request(`${actionUrl("testJsActionGet")}?echo=after-redeploy`).then((response) => { + cy.request({ + url: `${actionUrl("testJsActionGet")}?echo=after-redeploy`, + headers: JSON_HEADERS, + }).then((response) => { expect(response.status).to.equal(200); expect(response.body.echo).to.equal("after-redeploy"); }); diff --git a/tests/cypress/e2e/ui/choicelistInitializerTest.cy.ts b/tests/cypress/e2e/ui/choicelistInitializerTest.cy.ts index a6a783fd..d1fe4f5d 100644 --- a/tests/cypress/e2e/ui/choicelistInitializerTest.cy.ts +++ b/tests/cypress/e2e/ui/choicelistInitializerTest.cy.ts @@ -1,4 +1,12 @@ -import { GENERIC_SITE_KEY } from "../../support/constants"; +import { createSite, deleteSite } from "@jahia/cypress"; + +/** + * A site of its own, with French enabled: the shared test site is English-only, and asking its + * creation form for a `fr` content locale simply falls back to English — which would make the + * localization assertion below pass or fail depending on the Jahia version rather than on the + * initializer. + */ +const SITE_KEY = "jsChoicelistSite"; interface ValueConstraint { displayValue: string; @@ -11,40 +19,19 @@ interface Field { valueConstraints: ValueConstraint[]; } -const FORM_QUERY = ` - query createForm($nodeType: String!, $uiLocale: String!, $locale: String!, $uuidOrPath: String!) { - forms { - createForm(primaryNodeType: $nodeType, uiLocale: $uiLocale, locale: $locale, uuidOrPath: $uuidOrPath) { - sections { - fieldSets { - fields { - name - valueConstraints { - displayValue - value { string } - properties { name value } - } - } - } - } - } - } - } -`; - /** * Fetches the creation form of the test node type and returns its fields, flattened. Initializers * receive the CONTENT locale (the language being edited), not the UI locale. */ -const getFormFields = (locale: string): Cypress.Chainable => +const getFormFields = (locale: string, uiLocale = "en"): Cypress.Chainable => cy .apollo({ - query: FORM_QUERY, + queryFile: "graphql/createForm.graphql", variables: { nodeType: "javascriptExample:testChoicelistInitializer", - uiLocale: "en", + uiLocale, locale, - uuidOrPath: `/sites/${GENERIC_SITE_KEY}/home`, + uuidOrPath: `/sites/${SITE_KEY}/home`, }, }) .then((response) => @@ -65,6 +52,24 @@ const constraintLabel = (f: Field, value: string): string => f.valueConstraints.find((c) => c.value.string === value)?.displayValue; describe("JS choicelist initializers", () => { + before("Create a site with French enabled", () => { + cy.login(); + deleteSite(SITE_KEY); + createSite(SITE_KEY, { + languages: "en,fr", + templateSet: "javascript-modules-engine-test-module", + locale: "en", + serverName: "localhost", + }); + cy.logout(); + }); + + after("Remove the site", () => { + cy.login(); + deleteSite(SITE_KEY); + cy.logout(); + }); + beforeEach("Login", () => { cy.login(); }); @@ -107,8 +112,13 @@ describe("JS choicelist initializers", () => { }); }); - it("localizes labels through the content locale", () => { - getFormFields("fr").then((fields) => { + it("localizes labels", () => { + // Both locales are French: an initializer sees one of them, and which one is the platform's + // call — 8.2.1.0 forwards the content locale (asking for `uiLocale: "en", locale: "fr"` there + // still yields "Rouge"), while the snapshot CI runs answers "Red" for that same request. The + // module only decides what to do with the locale it is handed, so the test pins the + // localization, not the platform's routing of it. + getFormFields("fr", "fr").then((fields) => { expect(constraintLabel(field(fields, "color"), "red")).to.equal("Rouge"); }); }); diff --git a/tests/cypress/e2e/ui/nodeValidatorTest.cy.ts b/tests/cypress/e2e/ui/nodeValidatorTest.cy.ts index 3866eca6..dc93c880 100644 --- a/tests/cypress/e2e/ui/nodeValidatorTest.cy.ts +++ b/tests/cypress/e2e/ui/nodeValidatorTest.cy.ts @@ -5,28 +5,13 @@ import { GENERIC_SITE_KEY } from "../../support/constants"; const pageName = "testJsValidators"; const parentPath = `/sites/${GENERIC_SITE_KEY}/home/${pageName}/pagecontent`; -const ADD_NODE_MUTATION = ` - mutation addValidatedNode($parentPathOrId: String!, $name: String!, $properties: [InputJCRProperty!]) { - jcr { - addNode( - parentPathOrId: $parentPathOrId - name: $name - primaryNodeType: "javascriptExample:testValidation" - properties: $properties - ) { - uuid - } - } - } -`; - /** Attempts to create a testValidation node and yields the raw apollo response (errors included). */ const tryCreate = ( name: string, properties: Array<{ name: string; value: string; language?: string }>, ) => cy.apollo({ - mutation: ADD_NODE_MUTATION, + mutationFile: "graphql/addValidatedNode.graphql", variables: { parentPathOrId: parentPath, name, properties }, errorPolicy: "all", }); diff --git a/tests/cypress/fixtures/graphql/addValidatedNode.graphql b/tests/cypress/fixtures/graphql/addValidatedNode.graphql new file mode 100644 index 00000000..7aa75222 --- /dev/null +++ b/tests/cypress/fixtures/graphql/addValidatedNode.graphql @@ -0,0 +1,16 @@ +mutation addValidatedNode( + $parentPathOrId: String! + $name: String! + $properties: [InputJCRProperty!] +) { + jcr { + addNode( + parentPathOrId: $parentPathOrId + name: $name + primaryNodeType: "javascriptExample:testValidation" + properties: $properties + ) { + uuid + } + } +} diff --git a/tests/cypress/fixtures/graphql/createForm.graphql b/tests/cypress/fixtures/graphql/createForm.graphql new file mode 100644 index 00000000..66110e03 --- /dev/null +++ b/tests/cypress/fixtures/graphql/createForm.graphql @@ -0,0 +1,28 @@ +query createForm($nodeType: String!, $uiLocale: String!, $locale: String!, $uuidOrPath: String!) { + forms { + createForm( + primaryNodeType: $nodeType + uiLocale: $uiLocale + locale: $locale + uuidOrPath: $uuidOrPath + ) { + sections { + fieldSets { + fields { + name + valueConstraints { + displayValue + value { + string + } + properties { + name + value + } + } + } + } + } + } + } +}