From d79ec8f64cb402aa82853fcba11264dbf4d59c53 Mon Sep 17 00:00:00 2001
From: "harshitha.d"
Date: Tue, 24 Feb 2026 13:06:10 +0530
Subject: [PATCH 1/4] Add setLocale method to Asset class and corresponding
test case
---
.../com/contentstack/sdk/AssetTestCase.java | 16 ++++++++++
.../main/java/com/contentstack/sdk/Asset.java | 18 +++++++++++
.../contentstack/sdk/TestAssetAdvanced.java | 31 +++++++++++++++++++
3 files changed, 65 insertions(+)
diff --git a/contentstack/src/androidTest/java/com/contentstack/sdk/AssetTestCase.java b/contentstack/src/androidTest/java/com/contentstack/sdk/AssetTestCase.java
index ce358ec1..1f807b6e 100644
--- a/contentstack/src/androidTest/java/com/contentstack/sdk/AssetTestCase.java
+++ b/contentstack/src/androidTest/java/com/contentstack/sdk/AssetTestCase.java
@@ -124,6 +124,22 @@ public void onCompletion(ResponseType responseType, Error error) {
latch.await(5, TimeUnit.SECONDS);
}
+ @Test
+ public void test_setLocale_fetch() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Asset asset = stack.asset(assetUid);
+ asset.setLocale("en-us");
+ asset.fetch(new FetchResultCallback() {
+ @Override
+ public void onCompletion(ResponseType responseType, Error error) {
+ assertNotNull(asset.getAssetUid());
+ latch.countDown();
+ }
+ });
+ latch.await(5, TimeUnit.SECONDS);
+ assertEquals("Query was not completed in time", 0, latch.getCount());
+ }
+
@Test
public void test_include_branch() {
final Asset asset = stack.asset(assetUid);
diff --git a/contentstack/src/main/java/com/contentstack/sdk/Asset.java b/contentstack/src/main/java/com/contentstack/sdk/Asset.java
index c135e2a6..a30eb371 100755
--- a/contentstack/src/main/java/com/contentstack/sdk/Asset.java
+++ b/contentstack/src/main/java/com/contentstack/sdk/Asset.java
@@ -616,4 +616,22 @@ public Asset includeBranch() {
return this;
}
+ /**
+ *
+ *
Example :
+ *
+ * Asset asset = asset.setLocale("en-hi");
+ *
+ *
+ */
+ public Asset setLocale(String locale) {
+ if (locale != null) {
+ try {
+ urlQueries.put("locale", locale);
+ } catch (JSONException e) {
+ Log.e(TAG, e.getLocalizedMessage());
+ }
+ }
+ return this;
+ }
}
diff --git a/contentstack/src/test/java/com/contentstack/sdk/TestAssetAdvanced.java b/contentstack/src/test/java/com/contentstack/sdk/TestAssetAdvanced.java
index 8b12c8a9..fb3bd0a9 100644
--- a/contentstack/src/test/java/com/contentstack/sdk/TestAssetAdvanced.java
+++ b/contentstack/src/test/java/com/contentstack/sdk/TestAssetAdvanced.java
@@ -189,6 +189,37 @@ public void testAddParamOverwrite() {
assertNotNull(asset);
}
+ // ==================== SET LOCALE Tests ====================
+
+ @Test
+ public void testSetLocale() {
+ Asset result = asset.setLocale("en-us");
+ assertNotNull(result);
+ assertSame(asset, result);
+ assertEquals("en-us", asset.urlQueries.optString("locale", ""));
+ }
+
+ @Test
+ public void testSetLocaleReturnsThis() {
+ Asset result = asset.setLocale("en-hi");
+ assertSame(asset, result);
+ }
+
+ @Test
+ public void testSetLocaleWithNull() {
+ asset.setLocale("en-us");
+ Asset result = asset.setLocale(null);
+ assertSame(asset, result);
+ assertEquals("en-us", asset.urlQueries.optString("locale", ""));
+ }
+
+ @Test
+ public void testSetLocaleChainedWithFetch() {
+ asset.setLocale("en-us").includeFallback();
+ assertTrue(asset.urlQueries.has("locale"));
+ assertEquals("en-us", asset.urlQueries.optString("locale", ""));
+ }
+
// ==================== GET METHODS Tests ====================
@Test
From 1fa200aa1e91e85aea82d56d55f28960278681a2 Mon Sep 17 00:00:00 2001
From: "harshitha.d"
Date: Tue, 24 Feb 2026 13:06:27 +0530
Subject: [PATCH 2/4] Refactor TestContentType to simplify test methods
---
.../com/contentstack/sdk/TestContentType.java | 117 +++++++-----------
1 file changed, 47 insertions(+), 70 deletions(-)
diff --git a/contentstack/src/test/java/com/contentstack/sdk/TestContentType.java b/contentstack/src/test/java/com/contentstack/sdk/TestContentType.java
index c008c10d..c0409588 100644
--- a/contentstack/src/test/java/com/contentstack/sdk/TestContentType.java
+++ b/contentstack/src/test/java/com/contentstack/sdk/TestContentType.java
@@ -14,8 +14,10 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.Map;
-import static org.mockito.Mockito.*;
+import java.util.concurrent.atomic.AtomicReference;
+
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 28, manifest = Config.NONE)
public class TestContentType {
@@ -336,27 +338,8 @@ private ContentType createBareContentType(String contentTypeUid) {
return new ContentType(contentTypeUid);
}
- private ContentType createContentTypeWithStackAndHeaders(String contentTypeUid) throws Exception {
- ContentType contentType = new ContentType(contentTypeUid);
-
- // mock Stack and inject a stackHeader / localHeader field if present
- Stack mockStack = mock(Stack.class);
-
- // We will inject "localHeader" field into Stack if it exists
- try {
- Field localHeaderField = Stack.class.getDeclaredField("localHeader");
- localHeaderField.setAccessible(true);
- ArrayMap stackHeaders = new ArrayMap<>();
- stackHeaders.put("environment", "prod-env");
- stackHeaders.put("stackKey", "stackVal");
- localHeaderField.set(mockStack, stackHeaders);
- } catch (NoSuchFieldException ignored) {
- // If Stack doesn't have localHeader, getHeader will just use localHeader or
- // null.
- }
-
- contentType.setStackInstance(mockStack);
- return contentType;
+ private ContentType createContentTypeWithStackAndHeaders(String contentTypeUid) {
+ return stack.contentType(contentTypeUid);
}
private ArrayMap getLocalHeader(ContentType contentType) throws Exception {
@@ -484,82 +467,68 @@ public void testQueryHasFormHeaderNonNull() throws Exception {
@Test
public void testFetchWithEmptyContentTypeNameCallsOnRequestFail() throws Exception {
ContentType contentType = createBareContentType("");
-
- // make sure stackInstance is not null
- contentType.setStackInstance(mock(Stack.class));
-
- ContentTypesCallback callback = mock(ContentTypesCallback.class);
+ contentType.setStackInstance(stack);
+
+ final AtomicReference errorRef = new AtomicReference<>();
+ ContentTypesCallback callback = new ContentTypesCallback() {
+ @Override
+ public void onCompletion(ContentTypesModel contentTypesModel, Error error) {
+ if (error != null) {
+ errorRef.set(error);
+ }
+ }
+ };
contentType.fetch(new JSONObject(), callback);
- verify(callback).onRequestFail(eq(ResponseType.UNKNOWN), any(Error.class));
+ assertNotNull(errorRef.get());
}
@Test
public void testFetchExceptionCallsOnRequestFail() throws Exception {
ContentType contentType = createBareContentType("blog");
- contentType.setStackInstance(mock(Stack.class));
+ contentType.setStackInstance(stack);
- // Force an exception by using bad JSONObject for params
- JSONObject badParams = mock(JSONObject.class);
- when(badParams.keys()).thenThrow(new RuntimeException("boom"));
+ final AtomicReference errorRef = new AtomicReference<>();
+ ContentTypesCallback callback = new ContentTypesCallback() {
+ @Override
+ public void onCompletion(ContentTypesModel contentTypesModel, Error error) {
+ if (error != null) {
+ errorRef.set(error);
+ }
+ }
+ };
- ContentTypesCallback callback = mock(ContentTypesCallback.class);
+ contentType.fetch(new ThrowingJSONObject(), callback);
- contentType.fetch(badParams, callback);
-
- verify(callback).onRequestFail(eq(ResponseType.UNKNOWN), any(Error.class));
+ assertNotNull(errorRef.get());
}
@Test
public void testFetchNullParamsAndEnvironmentHeader() throws Exception {
ContentType contentType = createBareContentType("blog");
+ contentType.setStackInstance(stack);
- // Create a fake Stack with environment in its localHeader (so getHeader picks
- // it)
- Stack mockStack = mock(Stack.class);
-
- // Inject stack.localHeader if it exists
- try {
- Field localHeaderField = Stack.class.getDeclaredField("localHeader");
- localHeaderField.setAccessible(true);
- ArrayMap stackHeaders = new ArrayMap<>();
- stackHeaders.put("environment", "prod-env");
- localHeaderField.set(mockStack, stackHeaders);
- } catch (NoSuchFieldException ignored) {
- }
+ ContentTypesCallback callback = new ContentTypesCallback() {
+ @Override
+ public void onCompletion(ContentTypesModel contentTypesModel, Error error) {}
+ };
- // Inject VERSION field if exists so URL is built properly (not strictly
- // necessary for coverage)
- try {
- Field versionField = Stack.class.getDeclaredField("VERSION");
- versionField.setAccessible(true);
- versionField.set(mockStack, "v3");
- } catch (NoSuchFieldException ignored) {
- }
-
- contentType.setStackInstance(mockStack);
-
- ContentTypesCallback callback = mock(ContentTypesCallback.class);
-
- // this will hit:
- // if (params == null) params = new JSONObject();
- // then iterate keys (none)
- // then add environment if headers contains it
contentType.fetch(null, callback);
-
- // We don't verify callback interactions here; this is just to cover branches.
}
@Test
public void testFetchNormalCallDoesNotCrash() throws Exception {
ContentType contentType = createBareContentType("blog");
- contentType.setStackInstance(mock(Stack.class));
+ contentType.setStackInstance(stack);
JSONObject params = new JSONObject();
params.put("limit", 3);
- ContentTypesCallback callback = mock(ContentTypesCallback.class);
+ ContentTypesCallback callback = new ContentTypesCallback() {
+ @Override
+ public void onCompletion(ContentTypesModel contentTypesModel, Error error) {}
+ };
contentType.fetch(params, callback);
}
@@ -593,4 +562,12 @@ public void testGetUrlParamsNullOrEmptyReturnsNull() throws Exception {
HashMap resultEmpty = invokeGetUrlParams(contentType, empty);
assertNull(resultEmpty);
}
+
+ /** JSONObject that throws when keys() is called – used to trigger exception path in fetch() without Mockito. */
+ private static class ThrowingJSONObject extends JSONObject {
+ @Override
+ public Iterator keys() {
+ throw new RuntimeException("boom");
+ }
+ }
}
\ No newline at end of file
From 215833f64e069063f70e662aaba71d3c02dfb040 Mon Sep 17 00:00:00 2001
From: "harshitha.d"
Date: Tue, 24 Feb 2026 16:07:35 +0530
Subject: [PATCH 3/4] Update CHANGELOG for version 4.2.0, adding asset
localisation support and updating build.gradle to reflect the new version
number.
---
CHANGELOG.md | 6 ++++++
contentstack/build.gradle | 2 +-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 83882ab3..168be0f1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# CHANGELOG
+## Version 4.2.0
+
+### Date: 02-Mar-2026
+
+- Added asset localisation support
+
## Version 4.1.0
### Date: 15-Sept-2025
diff --git a/contentstack/build.gradle b/contentstack/build.gradle
index f7a3ed44..f0ad9685 100755
--- a/contentstack/build.gradle
+++ b/contentstack/build.gradle
@@ -7,7 +7,7 @@ plugins {
ext {
PUBLISH_GROUP_ID = 'com.contentstack.sdk'
PUBLISH_ARTIFACT_ID = 'android'
- PUBLISH_VERSION = '4.1.0'
+ PUBLISH_VERSION = '4.2.0'
}
android {
From 4e637542cd6fa0dde8bba29318557535bc6882e4 Mon Sep 17 00:00:00 2001
From: "harshitha.d"
Date: Tue, 24 Feb 2026 16:08:54 +0530
Subject: [PATCH 4/4] update license
---
LICENSE | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/LICENSE b/LICENSE
index c7d34c7c..0c2b6003 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2012 - 2025 Contentstack
+Copyright (c) 2012 - 2026 Contentstack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal