diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/ContentResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/ContentResource.java index c1a971b76ec1..d29f43c9329a 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/ContentResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/content/ContentResource.java @@ -412,7 +412,7 @@ public Response getContent(@Context HttpServletRequest request, if (-1 != depth) { ContentUtils.addRelationships(contentlet, user, mode, - languageId, depth, request, response); + contentlet.getLanguageId(), depth, request, response); } final String variant = contentlet.getVariantId(); contentlet = new DotTransformerBuilder().contentResourceOptions(false).content(contentlet).build().hydrate().get(0); diff --git a/dotcms-integration/src/test/java/com/dotcms/MainSuite1b.java b/dotcms-integration/src/test/java/com/dotcms/MainSuite1b.java index c820be5b3c2f..45bb5787239e 100644 --- a/dotcms-integration/src/test/java/com/dotcms/MainSuite1b.java +++ b/dotcms-integration/src/test/java/com/dotcms/MainSuite1b.java @@ -72,6 +72,7 @@ com.dotcms.rest.api.v1.page.PageResourceTest.class, com.dotcms.rest.api.v1.temp.TempFileResourceTest.class, com.dotcms.rest.api.v1.content.ContentVersionResourceIntegrationTest.class, + com.dotcms.rest.api.v1.content.ContentResourceIntegrationTest.class, com.dotcms.rest.api.v1.container.ContainerResourceIntegrationTest.class, com.dotcms.rest.api.v1.container.ContainerResourceHostResolutionIT.class, com.dotcms.rest.api.v1.theme.ThemeResourceIntegrationTest.class, diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/ContentResourceIntegrationTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/ContentResourceIntegrationTest.java new file mode 100644 index 000000000000..d11e698f2000 --- /dev/null +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/content/ContentResourceIntegrationTest.java @@ -0,0 +1,326 @@ +package com.dotcms.rest.api.v1.content; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import com.dotcms.contenttype.model.field.RelationshipField; +import com.dotcms.contenttype.model.type.ContentType; +import com.dotcms.datagen.ContentTypeDataGen; +import com.dotcms.datagen.ContentletDataGen; +import com.dotcms.datagen.FieldDataGen; +import com.dotcms.datagen.TestDataUtils; +import com.dotcms.mock.request.MockAttributeRequest; +import com.dotcms.mock.request.MockHeaderRequest; +import com.dotcms.mock.request.MockHttpRequestIntegrationTest; +import com.dotcms.mock.request.MockSessionRequest; +import com.dotcms.rest.ResponseEntityView; +import com.dotcms.util.IntegrationTestInitService; +import com.dotmarketing.business.APILocator; +import com.dotmarketing.portlets.contentlet.model.Contentlet; +import com.dotmarketing.portlets.contentlet.model.IndexPolicy; +import com.dotmarketing.portlets.languagesmanager.model.Language; +import com.dotmarketing.portlets.structure.model.Relationship; +import com.dotmarketing.util.Config; +import com.dotmarketing.util.WebKeys.Relationship.RELATIONSHIP_CARDINALITY; +import com.liferay.portal.model.User; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Integration tests for {@link ContentResource} (v1 endpoint: /api/v1/content). + */ +public class ContentResourceIntegrationTest { + + private static User systemUser; + + @BeforeClass + public static void prepare() throws Exception { + IntegrationTestInitService.getInstance().init(); + systemUser = APILocator.getUserAPI().getSystemUser(); + } + + /** + * Method to test: {@link ContentResource#getContent} + *
+ * Given scenario: + *
+ * Expected result: The parent should resolve via fallback to the default language, + * and the relationship should be hydrated using the contentlet's actual language (EN), + * returning the related child content correctly. + *
+ * Before the fix, the relationship was hydrated using the request's language (Spanish), + * which could fail to find the related content in that language. + */ + @Test + public void test_getContent_withFallbackLanguage_shouldHydrateRelationshipsWithContentletLanguage() + throws Exception { + + final Language defaultLanguage = APILocator.getLanguageAPI().getDefaultLanguage(); + final Language spanishLanguage = TestDataUtils.getSpanishLanguage(); + + // Save original config and enable fallback + final boolean originalConfig = Config.getBooleanProperty( + "DEFAULT_CONTENT_TO_DEFAULT_LANGUAGE", false); + Config.setProperty("DEFAULT_CONTENT_TO_DEFAULT_LANGUAGE", true); + + try { + // Create child content type (simple: title) + final ContentType childContentType = new ContentTypeDataGen() + .fields(List.of( + new FieldDataGen().name("Title") + .velocityVarName("title").next() + )) + .nextPersisted(); + + // Create parent content type with relationship field to child + final ContentType parentContentType = new ContentTypeDataGen() + .fields(List.of( + new FieldDataGen().name("Title") + .velocityVarName("title").next(), + new FieldDataGen().type(RelationshipField.class) + .name("Children") + .velocityVarName("children") + .relationType(childContentType.variable()) + .values(String.valueOf( + RELATIONSHIP_CARDINALITY.ONE_TO_MANY.ordinal())) + .next() + )) + .nextPersisted(); + + // Get the relationship object + final com.dotcms.contenttype.model.field.Field relationshipField = + parentContentType.fields(RelationshipField.class).get(0); + final Relationship relationship = APILocator.getRelationshipAPI() + .getRelationshipFromField(relationshipField, systemUser); + + // Create child content in default language (EN) only + final Contentlet childEN = new ContentletDataGen(childContentType.id()) + .languageId(defaultLanguage.getId()) + .setProperty("title", "Child EN Title") + .nextPersisted(); + + // Create parent content in default language (EN) with relationship to child + Contentlet parentEN = new ContentletDataGen(parentContentType.id()) + .languageId(defaultLanguage.getId()) + .setProperty("title", "Parent EN Title") + .next(); + parentEN.setIndexPolicy(IndexPolicy.FORCE); + parentEN = APILocator.getContentletAPI().checkin(parentEN, + Map.of(relationship, List.of(childEN)), systemUser, false); + + // Create new ContentResource instance (gets fresh Lazy config evaluation) + final ContentResource contentResource = new ContentResource(); + + // Build request with admin auth + final HttpServletRequest request = createAuthenticatedRequest(); + final HttpServletResponse response = mock(HttpServletResponse.class); + + // Request the parent with SPANISH language and depth=1 + // Parent doesn't exist in Spanish -> fallback to EN + // Fix ensures relationships are hydrated with EN (contentlet's language), + // not Spanish (request's language) + final Response endpointResponse = contentResource.getContent( + request, response, + parentEN.getIdentifier(), + String.valueOf(spanishLanguage.getId()), + "DEFAULT", + 1); + + // Verify the response is successful + assertEquals(Status.OK.getStatusCode(), endpointResponse.getStatus()); + + // Extract the contentlet map from the response + @SuppressWarnings("unchecked") + final ResponseEntityView