Skip to content

Commit f899bec

Browse files
Merge pull request #219 from contentstack/staging
DX | 27-01-2026 | Release
2 parents a7d0fda + e3dabc1 commit f899bec

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+927
-2103
lines changed

changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## v1.10.2
4+
5+
### Jan 27, 2026
6+
7+
- Delete Release fix
8+
39
## v1.10.1
410

511
### Jan 12, 2026

pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<artifactId>cms</artifactId>
88
<packaging>jar</packaging>
99
<name>contentstack-management-java</name>
10-
<version>1.10.1</version>
10+
<version>1.10.2</version>
1111
<description>Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an
1212
API-first approach
1313
</description>
@@ -245,6 +245,11 @@
245245
<version>3.0.0-M5</version>
246246
<configuration>
247247
<includes>
248+
<!-- Run all test files following Maven naming conventions -->
249+
<include>**/Test*.java</include>
250+
<include>**/*Test.java</include>
251+
<include>**/*Tests.java</include>
252+
<include>**/*TestCase.java</include>
248253
<include>**/*TestSuite.java</include>
249254
</includes>
250255
<reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>

src/main/java/com/contentstack/cms/Contentstack.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,12 @@ private OkHttpClient httpClient(Contentstack contentstack, Boolean retryOnFailur
858858
builder.addInterceptor(this.oauthInterceptor);
859859
} else {
860860
this.authInterceptor = contentstack.interceptor = new AuthInterceptor();
861+
862+
// Configure early access if needed
863+
if (this.earlyAccess != null) {
864+
this.authInterceptor.setEarlyAccess(this.earlyAccess);
865+
}
866+
861867
builder.addInterceptor(this.authInterceptor);
862868
}
863869

src/main/java/com/contentstack/cms/core/AuthInterceptor.java

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package com.contentstack.cms.core;
22

3+
import java.io.IOException;
4+
5+
import org.jetbrains.annotations.NotNull;
6+
37
import okhttp3.Interceptor;
48
import okhttp3.Request;
59
import okhttp3.Response;
6-
import org.jetbrains.annotations.NotNull;
7-
8-
import java.io.IOException;
910

1011
/**
1112
* <b>The type Header interceptor that extends Interceptor</b>
@@ -73,16 +74,42 @@ public void setEarlyAccess(String[] earlyAccess) {
7374
@Override
7475
public Response intercept(Chain chain) throws IOException {
7576
final String xUserAgent = Util.SDK_NAME + "/v" + Util.SDK_VERSION;
76-
Request.Builder request = chain.request().newBuilder().header(Util.X_USER_AGENT, xUserAgent).header(Util.USER_AGENT, Util.defaultUserAgent()).header(Util.CONTENT_TYPE, Util.CONTENT_TYPE_VALUE);
77+
Request originalRequest = chain.request();
78+
Request.Builder request = originalRequest.newBuilder()
79+
.header(Util.X_USER_AGENT, xUserAgent)
80+
.header(Util.USER_AGENT, Util.defaultUserAgent());
81+
82+
// Skip Content-Type header for DELETE /releases/{release_uid} request
83+
// to avoid "Body cannot be empty when content-type is set to 'application/json'" error
84+
if (!isDeleteReleaseRequest(originalRequest)) {
85+
request.header(Util.CONTENT_TYPE, Util.CONTENT_TYPE_VALUE);
86+
}
7787

7888
if (this.authtoken != null) {
7989
request.addHeader(Util.AUTHTOKEN, this.authtoken);
8090
}
81-
if (this.earlyAccess!=null && this.earlyAccess.length > 0) {
91+
92+
if (this.earlyAccess != null && this.earlyAccess.length > 0) {
8293
String commaSeparated = String.join(", ", earlyAccess);
8394
request.addHeader(Util.EARLY_ACCESS_HEADER, commaSeparated);
8495
}
8596
return chain.proceed(request.build());
8697
}
8798

99+
/**
100+
* Checks if the request is a DELETE request to /releases/{release_uid} endpoint.
101+
* This endpoint should not have Content-Type header as it doesn't accept a body.
102+
*
103+
* @param request The HTTP request to check
104+
* @return true if this is a DELETE /releases/{release_uid} request
105+
*/
106+
private boolean isDeleteReleaseRequest(Request request) {
107+
if (!"DELETE".equals(request.method())) {
108+
return false;
109+
}
110+
String path = request.url().encodedPath();
111+
// Match pattern: /v3/releases/{release_uid} (no trailing path segments)
112+
return path.matches(".*/releases/[^/]+$");
113+
}
114+
88115
}

src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,44 @@ public Response intercept(Chain chain) throws IOException {
4242
Request.Builder requestBuilder = originalRequest.newBuilder()
4343
.header("X-User-Agent", Util.defaultUserAgent())
4444
.header("User-Agent", Util.defaultUserAgent())
45-
.header("Content-Type", originalRequest.url().toString().contains("/token") ? "application/x-www-form-urlencoded" : "application/json")
4645
.header("x-header-ea", earlyAccess != null ? String.join(",", earlyAccess) : "true");
46+
47+
// Skip Content-Type header for DELETE /releases/{release_uid} request
48+
// to avoid "Body cannot be empty when content-type is set to 'application/json'" error
49+
if (!isDeleteReleaseRequest(originalRequest)) {
50+
String contentType = originalRequest.url().toString().contains("/token")
51+
? "application/x-www-form-urlencoded"
52+
: "application/json";
53+
requestBuilder.header("Content-Type", contentType);
54+
}
55+
4756
// Skip auth header for token endpoints
4857
if (!originalRequest.url().toString().contains("/token")) {
4958
if (oauthHandler.getTokens() != null && oauthHandler.getTokens().hasAccessToken()) {
5059
requestBuilder.header("Authorization", "Bearer " + oauthHandler.getAccessToken());
51-
5260
}
5361
}
5462

5563
// Execute request with retry and refresh handling
5664
return executeRequest(chain, requestBuilder.build(), 0);
5765
}
5866

67+
/**
68+
* Checks if the request is a DELETE request to /releases/{release_uid} endpoint.
69+
* This endpoint should not have Content-Type header as it doesn't accept a body.
70+
*
71+
* @param request The HTTP request to check
72+
* @return true if this is a DELETE /releases/{release_uid} request
73+
*/
74+
private boolean isDeleteReleaseRequest(Request request) {
75+
if (!"DELETE".equals(request.method())) {
76+
return false;
77+
}
78+
String path = request.url().encodedPath();
79+
// Match pattern: /v3/releases/{release_uid} (no trailing path segments)
80+
return path.matches(".*/releases/[^/]+$");
81+
}
82+
5983
private Response executeRequest(Chain chain, Request request, int retryCount) throws IOException {
6084
// Skip token refresh for token endpoints to avoid infinite loops
6185
if (request.url().toString().contains("/token")) {

src/test/java/com/contentstack/cms/ContentstackUnitTest.java

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ public class ContentstackUnitTest {
2424

2525
@Test
2626
void testDefaultClientInstance() {
27-
Contentstack client = new Contentstack.Builder().build();
28-
Assertions.assertEquals("api.contentstack.io", client.host);
29-
Assertions.assertEquals("443", client.port);
27+
Contentstack client = new Contentstack.Builder().build(); Assertions.assertEquals("443", client.port);
3028
Assertions.assertEquals("v3", client.version);
3129
Assertions.assertEquals(30, client.timeout);
3230
Assertions.assertNull(client.authtoken);
@@ -35,16 +33,12 @@ void testDefaultClientInstance() {
3533

3634
@Test
3735
void testClientDefaultPort() {
38-
Contentstack client = new Contentstack.Builder().build();
39-
Assertions.assertEquals("api.contentstack.io", client.host);
40-
Assertions.assertEquals("443", client.port);
36+
Contentstack client = new Contentstack.Builder().build(); Assertions.assertEquals("443", client.port);
4137
}
4238

4339
@Test
4440
void testClientDefaultHost() {
45-
Contentstack client = new Contentstack.Builder().build();
46-
Assertions.assertEquals("api.contentstack.io", client.host);
47-
}
41+
Contentstack client = new Contentstack.Builder().build(); }
4842

4943
@Test
5044
void testClientAPIDefaultVersion() {
@@ -191,7 +185,7 @@ void testSetOrganizations() {
191185
client.organization();
192186
} catch (Exception e) {
193187
System.out.println(e.getLocalizedMessage());
194-
Assertions.assertEquals("Please Login to access user instance", e.getLocalizedMessage());
188+
Assertions.assertEquals("Login or configure OAuth to continue. organization", e.getLocalizedMessage());
195189
}
196190
}
197191

@@ -203,7 +197,7 @@ void testSetAuthtokenLogin() {
203197
try {
204198
client.login("fake@email.com", "fake@password");
205199
} catch (Exception e) {
206-
Assertions.assertEquals("User is already loggedIn, Please logout then try to login again", e.getMessage());
200+
Assertions.assertEquals("Operation not allowed. You are already logged in.", e.getMessage());
207201
}
208202
Assertions.assertEquals("fake@authtoken", client.authtoken);
209203
}
@@ -216,7 +210,7 @@ void testSetAuthtokenLoginWithTfa() {
216210
params.put("tfaToken", "fake@tfa");
217211
client.login("fake@email.com", "fake@password", params);
218212
} catch (Exception e) {
219-
Assertions.assertEquals("User is already loggedIn, Please logout then try to login again", e.getMessage());
213+
Assertions.assertEquals("Operation not allowed. You are already logged in.", e.getMessage());
220214
}
221215
Assertions.assertEquals("fake@authtoken", client.authtoken);
222216
}

src/test/java/com/contentstack/cms/TestClient.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ public class TestClient {
2222
public final static String MANAGEMENT_TOKEN = (env.get("managementToken") != null) ? env.get("managementToken")
2323
: "managementToken99999999";
2424

25-
public final static String DEV_HOST = "api.contentstack.io";
26-
// (env.get("dev_host") != null) ? env.get("dev_host") : "api.contentstack.io";
25+
public final static String DEV_HOST = (env.get("dev_host") != null) ? env.get("dev_host").trim() : "api.contentstack.io";
2726
public final static String VARIANT_GROUP_UID = (env.get("variantGroupUid") != null) ? env.get("variantGroupUid")
2827
: "variantGroupUid99999999";
2928
private static Contentstack instance;
@@ -37,7 +36,10 @@ public static Contentstack getClient() {
3736
if (instance == null) {
3837
synchronized (Contentstack.class) {
3938
if (instance == null) {
40-
instance = new Contentstack.Builder().setAuthtoken(AUTHTOKEN).build();
39+
instance = new Contentstack.Builder()
40+
.setAuthtoken(AUTHTOKEN)
41+
.setHost(DEV_HOST)
42+
.build();
4143
}
4244
}
4345
}
@@ -69,7 +71,11 @@ public static Stack getStack() {
6971
if (stackInstance == null) {
7072
synchronized (Stack.class) {
7173
if (stackInstance == null) {
72-
stackInstance = new Contentstack.Builder().setAuthtoken(AUTHTOKEN).build().stack(headers);
74+
stackInstance = new Contentstack.Builder()
75+
.setAuthtoken(AUTHTOKEN)
76+
.setHost(DEV_HOST)
77+
.build()
78+
.stack(headers);
7379
}
7480
}
7581
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.contentstack.cms;
2+
3+
import com.contentstack.cms.core.AuthInterceptorTest;
4+
import com.contentstack.cms.stack.EnvironmentUnitTest;
5+
import com.contentstack.cms.stack.GlobalFieldUnitTests;
6+
import com.contentstack.cms.stack.LocaleUnitTest;
7+
import com.contentstack.cms.stack.ReleaseUnitTest;
8+
import org.junit.platform.runner.JUnitPlatform;
9+
import org.junit.platform.suite.api.SelectClasses;
10+
import org.junit.runner.RunWith;
11+
12+
/**
13+
* Unit Test Suite for running all unit tests
14+
* These tests don't require API access or credentials
15+
*
16+
* Note: Only public test classes can be included here.
17+
* Many unit test classes in the project are package-private and
18+
* cannot be referenced in this suite.
19+
*/
20+
@SuppressWarnings("deprecation")
21+
@RunWith(JUnitPlatform.class)
22+
@SelectClasses({
23+
// Core tests
24+
AuthInterceptorTest.class,
25+
ContentstackUnitTest.class,
26+
27+
// Stack module tests (only public classes)
28+
EnvironmentUnitTest.class,
29+
GlobalFieldUnitTests.class,
30+
LocaleUnitTest.class,
31+
ReleaseUnitTest.class
32+
})
33+
public class UnitTestSuite {
34+
}
35+

0 commit comments

Comments
 (0)