From 4877b4d10de3adcb20f529f6a247f38386a9075c Mon Sep 17 00:00:00 2001 From: Shailesh Date: Mon, 31 Jul 2023 19:38:30 +0530 Subject: [PATCH 001/197] v1.0.0 Aug 01, 2023 - Bug Fixed For un-localize An Entry - Fixed Timeout Issue - #32 Fixed - Code coverage improvements - NRP support added - General improvements with minor breaking changes --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3d67934b..10f7b829 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,6 @@ factoryConfiguration.json ### Eclipse ### .metadata -.env -.env/ test-report/ bin/ coverage/ From af8dc2a225e948a1be758887f87603508e720c74 Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Tue, 1 Aug 2023 19:47:10 +0530 Subject: [PATCH 002/197] Delete secrets-scan.yml Delete secrets-scan.yml --- .github/workflows/secrets-scan.yml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 .github/workflows/secrets-scan.yml diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml deleted file mode 100644 index 1e8f1761..00000000 --- a/.github/workflows/secrets-scan.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: Secrets Scan -on: - pull_request: - types: [opened, synchronize, reopened] -jobs: - security: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Gittyleaks - uses: gupy-io/gittyleaks-action@v0.1 \ No newline at end of file From 5e08f093ed08623c29c374006315e6e644ccbc4f Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Sat, 9 Sep 2023 00:09:37 +0530 Subject: [PATCH 003/197] Taxonomy and Terms --- pom.xml | 9 +- .../contentstack/cms/BaseImplementation.java | 14 +- .../cms/core/CMARuntimeException.java | 11 +- .../cms/organization/Organization.java | 26 +- .../com/contentstack/cms/stack/Alias.java | 84 ++++-- .../com/contentstack/cms/stack/Asset.java | 47 ++- .../com/contentstack/cms/stack/AuditLog.java | 37 ++- .../com/contentstack/cms/stack/Branch.java | 2 +- .../contentstack/cms/stack/BulkOperation.java | 6 +- .../com/contentstack/cms/stack/Compare.java | 2 +- .../contentstack/cms/stack/ContentType.java | 183 +++++++----- .../contentstack/cms/stack/DeliveryToken.java | 74 +++-- .../com/contentstack/cms/stack/Entry.java | 60 ++-- .../contentstack/cms/stack/Environment.java | 70 +++-- .../contentstack/cms/stack/Extensions.java | 73 +++-- .../com/contentstack/cms/stack/Folder.java | 101 ++++--- .../contentstack/cms/stack/GlobalField.java | 62 +++- .../com/contentstack/cms/stack/Label.java | 60 +++- .../com/contentstack/cms/stack/Locale.java | 67 +++-- .../cms/stack/ManagementToken.java | 45 ++- .../com/contentstack/cms/stack/Merge.java | 2 +- .../contentstack/cms/stack/PublishQueue.java | 87 ++++-- .../com/contentstack/cms/stack/Release.java | 79 +++-- .../contentstack/cms/stack/ReleaseItem.java | 69 ++++- .../com/contentstack/cms/stack/Roles.java | 111 ++++--- .../com/contentstack/cms/stack/Stack.java | 131 ++++++--- .../com/contentstack/cms/stack/Taxonomy.java | 254 ++++++++++++++++ .../cms/stack/TaxonomyService.java | 50 ++++ .../com/contentstack/cms/stack/Terms.java | 277 ++++++++++++++++++ .../com/contentstack/cms/stack/Webhook.java | 81 +++-- .../com/contentstack/cms/stack/Workflow.java | 110 ++++--- .../cms/ContentstackUnitTest.java | 6 +- .../cms/organization/OrgUnitTests.java | 9 - .../contentstack/cms/stack/AssetAPITest.java | 2 +- .../contentstack/cms/stack/TaxonomyTest.java | 269 +++++++++++++++++ 35 files changed, 2003 insertions(+), 567 deletions(-) create mode 100644 src/main/java/com/contentstack/cms/stack/Taxonomy.java create mode 100644 src/main/java/com/contentstack/cms/stack/TaxonomyService.java create mode 100644 src/main/java/com/contentstack/cms/stack/Terms.java create mode 100644 src/test/java/com/contentstack/cms/stack/TaxonomyTest.java diff --git a/pom.xml b/pom.xml index 3f726c2d..bfb9cdfb 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.0.0 + 1.1.0 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach @@ -165,6 +165,12 @@ ${junit-vintage-engine.version} test + + org.mockito + mockito-core + 2.8.9 + test + com.googlecode.json-simple json-simple @@ -328,6 +334,7 @@ org.apache.maven.plugins maven-pdf-plugin + 1.6.1 pdf diff --git a/src/main/java/com/contentstack/cms/BaseImplementation.java b/src/main/java/com/contentstack/cms/BaseImplementation.java index 766a966a..6246f887 100644 --- a/src/main/java/com/contentstack/cms/BaseImplementation.java +++ b/src/main/java/com/contentstack/cms/BaseImplementation.java @@ -7,12 +7,11 @@ /** * The interface @{@link BaseImplementation} */ -public interface BaseImplementation { +public interface BaseImplementation { /** * The function adds a parameter to a collection using a key-value pair. * - * @param the type of the parameter * @param key A string representing the key of the parameter. It cannot be * null and must be * provided as a non-null value. @@ -21,12 +20,11 @@ public interface BaseImplementation { * object as its value. * @return The method is returning an object of type T. */ - T addParam(@NotNull String key, @NotNull Object value); + T addParam(@NotNull String key, @NotNull Object value); /** * The function adds a header with a key-value pair to a request. * - * @param the type of the parameter * @param key The key parameter is a string that represents the name or * identifier of the header. * It is used to specify the type of information being sent in the @@ -35,30 +33,28 @@ public interface BaseImplementation { * header. * @return The method is returning an object of type T. */ - T addHeader(@NotNull String key, @NotNull String value); + T addHeader(@NotNull String key, @NotNull String value); /** * The function "addParams" takes a HashMap of String keys and Object values as * input and returns a * generic type T. * - * @param the type of the parameter * @param params The "params" parameter is a HashMap that maps String keys to * Object values. It is * annotated with @NotNull, indicating that it cannot be null. * @return The method is returning an object of type T. */ - T addParams(@NotNull HashMap params); + T addParams(@NotNull HashMap params); /** * The function adds headers to a HashMap. * - * @param the type of the parameter * @param headers A HashMap containing key-value pairs of headers, where the key * is a String * representing the header name and the value is a String * representing the header value. * @return The method is returning an object of type T. */ - T addHeaders(@NotNull HashMap headers); + T addHeaders(@NotNull HashMap headers); } diff --git a/src/main/java/com/contentstack/cms/core/CMARuntimeException.java b/src/main/java/com/contentstack/cms/core/CMARuntimeException.java index 0008342b..5ccb2167 100644 --- a/src/main/java/com/contentstack/cms/core/CMARuntimeException.java +++ b/src/main/java/com/contentstack/cms/core/CMARuntimeException.java @@ -8,9 +8,14 @@ * @since 2022-10-20 */ public class CMARuntimeException extends Exception { - // The code `public CMARuntimeException(String message) { super(message); }` is - // defining a constructor - // for the `CMARuntimeException` class. + + /** + * The code `public CMARuntimeException(String message) { super(message); }` is + * defining a constructor + * for the `CMARuntimeException` class. + * + * @param message the message for exception + */ public CMARuntimeException(String message) { super(message); } diff --git a/src/main/java/com/contentstack/cms/organization/Organization.java b/src/main/java/com/contentstack/cms/organization/Organization.java index 5bea691e..dfdae44d 100644 --- a/src/main/java/com/contentstack/cms/organization/Organization.java +++ b/src/main/java/com/contentstack/cms/organization/Organization.java @@ -1,5 +1,6 @@ package com.contentstack.cms.organization; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; @@ -19,7 +20,7 @@ * @version v0.1.0 * @since 2022-10-20 */ -public class Organization { +public class Organization implements BaseImplementation { private final OrganizationService service; protected HashMap headers; @@ -65,6 +66,18 @@ public Organization addHeader(@NotNull String key, @NotNull String value) { return this; } + @Override + public Organization addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + @Override + public Organization addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; + } + /** * Sets header for the request * @@ -89,17 +102,6 @@ protected Organization clearParams() { return this; } - /** - * Sets header for the request - * - * @param key header key for the request - * @return instance of {@link Organization} - */ - public Organization removeParam(@NotNull String key) { - this.params.remove(key); - return this; - } - /** * Gets all organizations.
* The Get all organizations call lists all organizations related to the diff --git a/src/main/java/com/contentstack/cms/stack/Alias.java b/src/main/java/com/contentstack/cms/stack/Alias.java index 0495f79c..317ff70c 100644 --- a/src/main/java/com/contentstack/cms/stack/Alias.java +++ b/src/main/java/com/contentstack/cms/stack/Alias.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; @@ -18,12 +19,12 @@ * @author ***REMOVED*** * @version v0.1.0 * @see About - * Aliases - * + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#aliases">About + * Aliases + * * @since 2022-10-20 */ -public class Alias { +public class Alias implements BaseImplementation { protected final Map headers; protected Map params; @@ -57,21 +58,24 @@ protected Alias(Retrofit instance, String aliasUid) { /** * The addHeader function adds a key-value pair to the headers map. - * + * * @param key A string representing the key of the header. This is used to * identify the header when * retrieving or modifying it. * @param value The value parameter is of type Object, which means it can accept * any type of object as * its value. + * @return instance of the `Alias` class */ - public void addHeader(@NotNull String key, @NotNull Object value) { + @Override + public Alias addHeader(@NotNull String key, @NotNull String value) { this.headers.put(key, value); + return this; } /** * The addParam function adds a key-value pair to a map. - * + * * @param key A string representing the key for the parameter. It is annotated * with @NotNull, * indicating that it cannot be null. @@ -79,14 +83,45 @@ public void addHeader(@NotNull String key, @NotNull Object value) { * any type of object as * its value. */ - public void addParam(@NotNull String key, @NotNull Object value) { + @Override + public Alias addParam(@NotNull String key, @NotNull Object value) { this.params.put(key, value); + return this; + } + + /** + * The addParam function adds a key-value pair to Alias + * + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of {@link Alias} + */ + @Override + public Alias addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + /** + * The removeParam function removes a key-value pair from + * + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of Alias + */ + @Override + public Alias addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; } /** * The removeParam function removes a parameter from a map using the specified * key. - * + * * @param key The parameter "key" is of type String and is used to specify the * key of the parameter * that needs to be removed from the "params" collection. @@ -107,9 +142,9 @@ protected void clearParams() { * @return Call * @author ***REMOVED*** * @see Get - * all - * aliases + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-all-aliases">Get + * all + * aliases * @since 2022-10-20 */ public Call find() { @@ -122,12 +157,12 @@ public Call find() { * @return Call * @author ***REMOVED*** * @see - * Get a single branch + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-a-single-branch"> + * Get a single branch * @since 2022-10-20 */ public Call fetch() { - Objects.requireNonNull(this.uid,"Global Field Uid can not be null or empty"); + Objects.requireNonNull(this.uid, "Global Field Uid can not be null or empty"); return this.service.single(this.headers, this.uid); } @@ -142,15 +177,14 @@ public Call fetch() { * “Body” section, you need to provide * the UID of the new target branch that will be associated with the alias. * - * @param body - * the request body + * @param body the request body * @return Call * @author ***REMOVED*** * @see Update - * a - * branch - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#assign-or-update-an-alias">Update + * a + * branch + * @see #addHeader(String, String) to add headers * @since 2022-10-20 */ public Call update(@NotNull JSONObject body) { @@ -169,14 +203,14 @@ public Call update(@NotNull JSONObject body) { * @return Call * @author ***REMOVED*** * @see Delete - * a branch - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#delete-an-alias">Delete + * a branch + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query params * @since 2022-10-20 */ public Call delete() { - Objects.requireNonNull(this.uid,"Global Field Uid can not be null or empty"); + Objects.requireNonNull(this.uid, "Global Field Uid can not be null or empty"); return this.service.delete(this.headers, this.uid, this.params); } diff --git a/src/main/java/com/contentstack/cms/stack/Asset.java b/src/main/java/com/contentstack/cms/stack/Asset.java index 1fdabc09..aa3e531e 100644 --- a/src/main/java/com/contentstack/cms/stack/Asset.java +++ b/src/main/java/com/contentstack/cms/stack/Asset.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.RequestBody; @@ -24,7 +25,7 @@ * @author ***REMOVED*** * @since 2022-10-20 */ -public class Asset { +public class Asset implements BaseImplementation { protected final Map headers; protected Map params; @@ -50,26 +51,62 @@ protected Asset(Retrofit instance, Map header, String uid) { } - public Asset addParam(String key, Object value) { + /** + * @param key A string representing the key of the parameter. It cannot be + * null and must be + * provided as a non-null value. + * @param value The "value" parameter is of type Object, which means it can + * accept any type of + * object as its value. + * @return instance of Asset + */ + public Asset addParam(@NotNull String key, @NotNull Object value) { this.params.put(key, value); return this; } - public Asset addHeader(String key, String value) { + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of Asset + */ + public Asset addHeader(@NotNull String key, @NotNull String value) { this.headers.put(key, value); return this; } - public Asset addHeaders(HashMap headers) { + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of Asset + */ + public Asset addHeaders(@NotNull HashMap headers) { this.headers.putAll(headers); return this; } - public Asset addParams(HashMap headers) { + + /** + * @param headers The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of Asset + */ + public Asset addParams(@NotNull HashMap headers) { this.params.putAll(headers); return this; } + + /** + * clears all params in the request + */ protected void clearParams() { this.params.clear(); } diff --git a/src/main/java/com/contentstack/cms/stack/AuditLog.java b/src/main/java/com/contentstack/cms/stack/AuditLog.java index c17d559c..eac83421 100644 --- a/src/main/java/com/contentstack/cms/stack/AuditLog.java +++ b/src/main/java/com/contentstack/cms/stack/AuditLog.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import retrofit2.Call; @@ -29,7 +30,7 @@ * @version v0.1.0 * @since 2022-10-22 */ -public class AuditLog { +public class AuditLog implements BaseImplementation { protected final AuditLogService service; protected HashMap headers; @@ -49,24 +50,28 @@ protected AuditLog(Retrofit retrofit, String uid) { this.service = retrofit.create(AuditLogService.class); } - /** - * Sets header for the request - * - * @param key header key for the request - * @param value header value for the request - */ - public void addHeader(@NotNull String key, @NotNull Object value) { + @Override + public AuditLog addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + @Override + public AuditLog addHeader(@NotNull String key, @NotNull String value) { this.headers.put(key, value); + return this; } - /** - * Sets header for the request - * - * @param key header key for the request - * @param value header value for the request - */ - public void addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); + @Override + public AuditLog addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + @Override + public AuditLog addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; } /** diff --git a/src/main/java/com/contentstack/cms/stack/Branch.java b/src/main/java/com/contentstack/cms/stack/Branch.java index 640118d2..68254d96 100644 --- a/src/main/java/com/contentstack/cms/stack/Branch.java +++ b/src/main/java/com/contentstack/cms/stack/Branch.java @@ -26,7 +26,7 @@ * * @since 2022-10-22 */ -public class Branch implements BaseImplementation { +public class Branch implements BaseImplementation { protected final Map headers; protected Map params; diff --git a/src/main/java/com/contentstack/cms/stack/BulkOperation.java b/src/main/java/com/contentstack/cms/stack/BulkOperation.java index a27bc574..2e49ed32 100644 --- a/src/main/java/com/contentstack/cms/stack/BulkOperation.java +++ b/src/main/java/com/contentstack/cms/stack/BulkOperation.java @@ -27,7 +27,7 @@ * Bulk Operations Queue * @since 2023 -08-23 */ -public class BulkOperation implements BaseImplementation { +public class BulkOperation implements BaseImplementation { /** * The Service. @@ -124,7 +124,6 @@ public Call publish(@NotNull JSONObject body) { * satisfy the publish rules, pass * additional query parameters, skip_workflow_stage_check=true and * approvals=true. - *

* * @param body the body * @return Call call @@ -154,7 +153,6 @@ public Call unpublish(@NotNull JSONObject body) { * Authtoken (any one is mandatory), * along with the stack API key, to make a valid Content Management API request. * Read more about authentication. - *

* * @param body the body * @return Call call @@ -188,7 +186,7 @@ public Call delete(JSONObject body) { *

* * @param body the body - * @return Call call + * @return Call * @see Bulk * Delete Operation diff --git a/src/main/java/com/contentstack/cms/stack/Compare.java b/src/main/java/com/contentstack/cms/stack/Compare.java index 197be7db..06e456e9 100644 --- a/src/main/java/com/contentstack/cms/stack/Compare.java +++ b/src/main/java/com/contentstack/cms/stack/Compare.java @@ -36,7 +36,7 @@ * "https://www.contentstack.com/docs/developers/apis/content-management-api/#compare-branches">Compare * Branches */ -public class Compare implements BaseImplementation { +public class Compare implements BaseImplementation { private final CompareService service; private final Map headers; diff --git a/src/main/java/com/contentstack/cms/stack/ContentType.java b/src/main/java/com/contentstack/cms/stack/ContentType.java index 7329cf3a..c26de5a2 100644 --- a/src/main/java/com/contentstack/cms/stack/ContentType.java +++ b/src/main/java/com/contentstack/cms/stack/ContentType.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; @@ -28,10 +29,10 @@ * @author ***REMOVED*** * @version v0.1.0 * @see Content - * Types + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#content-types">Content + * Types */ -public class ContentType { +public class ContentType implements BaseImplementation { protected final ContentTypeService service; protected final Map headers; @@ -42,10 +43,8 @@ public class ContentType { /** * Instantiates a new Content type. * - * @param instance - * the {@link Retrofit} instance - * @param headers - * the headers + * @param instance the {@link Retrofit} instance + * @param headers the headers */ protected ContentType(@NotNull Retrofit instance, Map headers) { this.instance = instance; @@ -58,12 +57,9 @@ protected ContentType(@NotNull Retrofit instance, Map headers) { /** * Instantiates a new Content type. * - * @param instance - * the {@link Retrofit} instance - * @param headers - * the headers - * @param uid - * the contentTypeUid + * @param instance the {@link Retrofit} instance + * @param headers the headers + * @param uid the contentTypeUid */ public ContentType(@NotNull Retrofit instance, Map headers, String uid) { this.instance = instance; @@ -74,35 +70,70 @@ public ContentType(@NotNull Retrofit instance, Map headers, Stri this.service = instance.create(ContentTypeService.class); } + + //---------------------------------------------------------------- + // GETTERS + //---------------------------------------------------------------- + /** - * Sets header for the request - * - * @param key - * header key for the request - * @param value - * header value for the request + * @param key A string representing the key of the parameter. It cannot be + * null and must be + * provided as a non-null value. + * @param value The "value" parameter is of type Object, which means it can + * accept any type of + * object as its value. + * @return instance of ContentType + */ + @Override + public ContentType addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of ContentType */ - public void addHeader(@NotNull String key, @NotNull Object value) { + @Override + public ContentType addHeader(@NotNull String key, @NotNull String value) { this.headers.put(key, value); + return this; } /** - * Sets header for the request - * - * @param key - * key of query parameters of request - * @param value - * value of query parameters of request + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of ContentType */ - public void addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); + @Override + public ContentType addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of ContentType + */ + @Override + public ContentType addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; } /** * Sets header for the request * - * @param key - * Remove query parameters of request by key + * @param key remove query parameters of request by key */ public void removeParam(@NotNull String key) { this.params.remove(key); @@ -128,8 +159,7 @@ public Entry entry() { /** * An entry is the actual piece of content created using one of the defined * - * @param entryUid - * The entry uid + * @param entryUid The entry uid * @return Entry */ public Entry entry(@NotNull String entryUid) { @@ -156,9 +186,9 @@ public Entry entry(@NotNull String entryUid) { * * @return the call * @see - * Get all content types - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-all-content-types"> + * Get all content types + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query params * @since 0.1.0 */ @@ -206,10 +236,10 @@ public Call find() { * * @return Call * @see - * Get a - * single content type - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-a-single-content-type"> + * Get a + * single content type + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query params * @since 0.1.0 */ @@ -250,13 +280,12 @@ public Call fetch() { * {@link Call} response = contentType.create(managementToken, bodyStr).execute(); * * - * @param requestBody - * the request body + * @param requestBody the request body * @return Call * @see - * Create A Content Type - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#create-a-content-type"> + * Create A Content Type + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query params * @since 0.1.0 */ @@ -277,13 +306,12 @@ public Call create(JSONObject requestBody) { * href=https://www.contentstack.com/docs/developers/apis/content-management-api/#update-content-type>Read * more * - * @param requestBody - * the request body + * @param requestBody the request body * @return Call * @see - * Update Content Type - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#update-content-type"> + * Update Content Type + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query params * @since 0.1.0 */ @@ -308,13 +336,12 @@ public Call update(JSONObject requestBody) { * required fields to the content type and saved it) or while editing a content * type (both via UI and API). * - * @param requestBody - * the request body JSONBody + * @param requestBody the request body JSONBody * @return Call * @see - * Set Field Visibility Rule for Content Type - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#set-field-visibility-rule-for-content-type"> + * Set Field Visibility Rule for Content Type + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query params * @since 0.1.0 */ @@ -339,9 +366,9 @@ public Call fieldVisibilityRule(JSONObject requestBody) { * * @return Call * @see - * Delete Content Type - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#delete-content-type"> + * Delete Content Type + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query params * @since 0.1.0 */ @@ -367,13 +394,12 @@ public Call delete() { * Management API request. Read more about * authentication. * - * @param isIncludeGlobalField - * Include Global Field true/false + * @param isIncludeGlobalField Include Global Field true/false * @return Call * @see - * Get All References Of Content Type - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-all-references-of-content-type"> + * Get All References Of Content Type + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call reference(Boolean isIncludeGlobalField) { @@ -399,9 +425,9 @@ public Call reference(Boolean isIncludeGlobalField) { * * @return Call * @see - * Get All References Of Content Type - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-all-references-of-content-type"> + * Get All References Of Content Type + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call referenceIncludeGlobalField() { @@ -426,9 +452,9 @@ public Call referenceIncludeGlobalField() { * * @return Call * @see - * Export A Content Type - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#export-a-content-type"> + * Export A Content Type + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call export() { @@ -451,15 +477,14 @@ public Call export() { * valid Content Management API request. * Read more about authentication. * - * @param version - * The version of content type you want to retrieve. If no + * @param version The version of content type you want to retrieve. If no * version is specified, you will get the latest * version of the content type * @return Call * @see - * Export A Content Type - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#export-a-content-type"> + * Export A Content Type + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call export(int version) { @@ -476,9 +501,9 @@ public Call export(int version) { * * @return Call * @see - * Import A Content Type - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#import-a-content-type"> + * Import A Content Type + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call imports() { @@ -498,9 +523,9 @@ public Call imports() { * * @return Call * @see - * Import A Content Type - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#import-a-content-type"> + * Import A Content Type + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call importOverwrite() { diff --git a/src/main/java/com/contentstack/cms/stack/DeliveryToken.java b/src/main/java/com/contentstack/cms/stack/DeliveryToken.java index 3708a42a..fe470d5d 100644 --- a/src/main/java/com/contentstack/cms/stack/DeliveryToken.java +++ b/src/main/java/com/contentstack/cms/stack/DeliveryToken.java @@ -1,6 +1,6 @@ package com.contentstack.cms.stack; -import com.contentstack.cms.organization.Organization; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; @@ -23,7 +23,7 @@ * @version v0.1.0 * @since 2022-10-22 */ -public class DeliveryToken { +public class DeliveryToken implements BaseImplementation { protected final TokenService service; protected HashMap headers; @@ -44,21 +44,6 @@ protected DeliveryToken(TokenService service, @NotNull String tokenUid) { this.service = service; } - /** - * The addHeader function adds a key-value pair to the headers map. - * - * @param key A string representing the key of the header. This is used to - * identify the header when - * retrieving or modifying it. - * @param value The value parameter is of type Object, which means it can accept - * any type of object as - * its value. - * @return instance of {@link DeliveryToken} - */ - public DeliveryToken addHeader(@NotNull String key, @NotNull Object value) { - this.headers.put(key, value); - return this; - } /** * The addParam function adds a key-value pair to a map. @@ -69,13 +54,54 @@ public DeliveryToken addHeader(@NotNull String key, @NotNull Object value) { * @param value The value parameter is of type Object, which means it can accept * any type of object as * its value. - * @return instance of {@link DeliveryToken} + * @return instance of {@link DeliveryToken} */ + @Override public DeliveryToken addParam(@NotNull String key, @NotNull Object value) { this.params.put(key, value); return this; } + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of {@link DeliveryToken} + */ + @Override + public DeliveryToken addHeader(@NotNull String key, @NotNull String value) { + this.headers.put(key, value); + return this; + } + + /** + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of {@link DeliveryToken} + */ + @Override + public DeliveryToken addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of {@link DeliveryToken} + */ + @Override + public DeliveryToken addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; + } + /** * The function removes a parameter from a map using the specified key. * @@ -101,7 +127,7 @@ protected DeliveryToken clearParams() { * all * Delivery Tokens * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -119,7 +145,7 @@ public Call find() { * a * Single Delivery Token * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -143,7 +169,7 @@ public Call fetch() { * Delivery * Token * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -171,7 +197,7 @@ public Call create(@NotNull JSONObject requestBody) { * Delivery * Token * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -189,7 +215,7 @@ public Call update(@NotNull JSONObject requestBody) { * Delivery * Token * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -208,7 +234,7 @@ public Call delete() { * Delivery * Token * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ diff --git a/src/main/java/com/contentstack/cms/stack/Entry.java b/src/main/java/com/contentstack/cms/stack/Entry.java index 4514506b..13960a9f 100644 --- a/src/main/java/com/contentstack/cms/stack/Entry.java +++ b/src/main/java/com/contentstack/cms/stack/Entry.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; @@ -25,7 +26,7 @@ * @version v0.1.0 * @since 2022-10-22 */ -public class Entry { +public class Entry implements BaseImplementation { final String ERROR_ENTRY_UID = "Entry UID Is Required"; final String ERROR_CT_UID = "Content Type UID Is Required"; @@ -66,9 +67,10 @@ private void validateCT() { * * @param key header key for the request * @param value header value for the request - * @return instance of {@link Entry} + * @return instance of {@link Entry} */ - public Entry addHeader(@NotNull String key, @NotNull Object value) { + @Override + public Entry addHeader(@NotNull String key, @NotNull String value) { this.headers.put(key, value); return this; } @@ -78,13 +80,26 @@ public Entry addHeader(@NotNull String key, @NotNull Object value) { * * @param key query param key for the request * @param value query param value for the request - * @return instance of {@link Entry} + * @return instance of {@link Entry} */ public Entry addParam(@NotNull String key, @NotNull Object value) { this.params.put(key, value); return this; } + + @Override + public Entry addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + @Override + public Entry addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; + } + /** * Set header for the request * @@ -97,6 +112,7 @@ public Entry removeParam(@NotNull String key) { /** * To clear all the query params + * * @return instance of {@link Entry} */ protected Entry clearParams() { @@ -194,7 +210,7 @@ public Call fetch() { * @see Create * A Entry - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -221,7 +237,7 @@ public Call create(JSONObject requestBody) { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#update-an-entry"> * Update * an entry - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -275,7 +291,7 @@ public Call update(JSONObject requestBody) { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#atomic-updates-to-entries"> * Atomic * Operation - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -317,7 +333,7 @@ public Call atomicOperation(JSONObject requestBody) { * @see Get * Delete An Entry - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -367,7 +383,7 @@ public Call delete() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#delete-an-entry"> * Delete * An Entry - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -393,7 +409,7 @@ public Call delete(JSONObject requestBody) { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#set-version-name-for-entry"> * Set * Version Name for Entry - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call versionName(int version, JSONObject requestBody) { @@ -434,7 +450,7 @@ public Call versionName(int version, JSONObject requestBody) { * @see * Get Details of All Versions of an Entry - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call detailOfAllVersion() { @@ -453,7 +469,7 @@ public Call detailOfAllVersion() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#delete-version-name-of-entry"> * Delete * Version Name of Entry - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call deleteVersionName(int versionNumber, JSONObject requestBody) { @@ -481,7 +497,7 @@ public Call deleteVersionName(int versionNumber, JSONObject reques * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-references-of-an-entry"> * Get * references of an entry - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -505,7 +521,7 @@ public Call getReference() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-languages-of-an-entry"> * Get * language of an entry - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -537,7 +553,7 @@ public Call getLanguage() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#localize-an-entry"> * Localize an entry * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call localize(@NotNull JSONObject requestBody, @@ -558,7 +574,7 @@ public Call localize(@NotNull JSONObject requestBody, * "https://www.contentstack.com/docs/developers/apis/content-management-api/#unlocalize-an-entry"> * unlocalize an entry * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call unLocalize(@NotNull String localeCode) { @@ -578,7 +594,7 @@ public Call unLocalize(@NotNull String localeCode) { * Export * an entry * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -614,7 +630,7 @@ public Call export() { * Import * an entry * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -642,7 +658,7 @@ public Call imports() { * Import * an entry * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -688,7 +704,7 @@ public Call importExisting() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#publish-an-entry"> * Publish an entry * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call publish(@NotNull JSONObject requestBody) { @@ -718,7 +734,7 @@ public Call publish(@NotNull JSONObject requestBody) { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#publish-an-entry-with-references"> * Publish an entry With reference * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -752,7 +768,7 @@ public Call publishWithReference(@NotNull JSONObject requestBody) * "https://www.contentstack.com/docs/developers/apis/content-management-api/#unpublish-an-entry"> * Unpublish an entry * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call unpublish(@NotNull JSONObject requestBody) { diff --git a/src/main/java/com/contentstack/cms/stack/Environment.java b/src/main/java/com/contentstack/cms/stack/Environment.java index 28d0032b..29b39a33 100644 --- a/src/main/java/com/contentstack/cms/stack/Environment.java +++ b/src/main/java/com/contentstack/cms/stack/Environment.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; @@ -19,7 +20,7 @@ * @version v0.1.0 * @since 2022-10-22 */ -public class Environment { +public class Environment implements BaseImplementation { protected final Map headers; protected final Map params; @@ -47,23 +48,58 @@ void validate() { } /** - * Sets header for the request - * - * @param key header key for the request - * @param value header value for the request + * @param key A string representing the key of the parameter. It cannot be + * null and must be + * provided as a non-null value. + * @param value The "value" parameter is of type Object, which means it can + * accept any type of + * object as its value. + * @return instance of the Environment + */ + @Override + public Environment addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of the Environment */ - public void addHeader(@NotNull String key, @NotNull Object value) { + @Override + public Environment addHeader(@NotNull String key, @NotNull String value) { this.headers.put(key, value); + return this; } /** - * Sets header for the request - * - * @param key query param key for the request - * @param value query param value for the request + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of the Environment */ - public void addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); + @Override + public Environment addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of the Environment + */ + @Override + public Environment addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; } /** @@ -104,7 +140,7 @@ protected void clearParams() { * all * environments * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -126,7 +162,7 @@ public Call find() { * a single * environments * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -154,7 +190,7 @@ public Call fetch() { * all * environments * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -181,7 +217,7 @@ public Call create(@NotNull JSONObject body) { * @see Update * Environment - * @see #addHeader(String, Object) to add headers to the request + * @see #addHeader(String, String) to add headers to the request * @since 0.1.0 */ public Call update(@NotNull JSONObject requestBody) { @@ -201,7 +237,7 @@ public Call update(@NotNull JSONObject requestBody) { * @see Delete * Environment - * @see #addHeader(String, Object) to add headers to the request + * @see #addHeader(String, String) to add headers to the request * @since 0.1.0 */ public Call delete() { diff --git a/src/main/java/com/contentstack/cms/stack/Extensions.java b/src/main/java/com/contentstack/cms/stack/Extensions.java index 260c370a..8ccb3f57 100644 --- a/src/main/java/com/contentstack/cms/stack/Extensions.java +++ b/src/main/java/com/contentstack/cms/stack/Extensions.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import com.contentstack.cms.core.Util; import okhttp3.RequestBody; import okhttp3.ResponseBody; @@ -28,7 +29,7 @@ * @version v0.1.0 * @since 2022-10-22 */ -public class Extensions { +public class Extensions implements BaseImplementation { protected final ExtensionsService service; protected HashMap headers; @@ -53,24 +54,60 @@ void validate() { throw new IllegalAccessError("Custom Field UID Can Not Be Null OR Empty"); } + /** - * Sets header for the request - * - * @param key header key for the request - * @param value header value for the request + * @param key A string representing the key of the parameter. It cannot be + * null and must be + * provided as a non-null value. + * @param value The "value" parameter is of type Object, which means it can + * accept any type of + * object as its value. + * @return instance of {@link Extensions} + */ + @Override + public Extensions addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of {@link Extensions} */ - public void addHeader(@NotNull String key, @NotNull Object value) { + @Override + public Extensions addHeader(@NotNull String key, @NotNull String value) { this.headers.put(key, value); + return this; } /** - * Sets header for the request - * - * @param key query param key for the request - * @param value query param value for the request + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of {@link Extensions} */ - public void addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); + @Override + public Extensions addParams(@NotNull HashMap params) { + this.params.putAll(params); + return null; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of {@link Extensions} + */ + @Override + public Extensions addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; } /** @@ -103,7 +140,7 @@ protected void clearParams() { * * * @return Call call - * @see #addHeader(String, Object) #addHeader(String, Object)#addHeader(String, + * @see #addHeader(String, String) #addHeader(String, Object)#addHeader(String, * Object)to add headers * @see #addParam(String, Object) #addParam(String, Object)#addParam(String, * Object)to add query parameters @@ -120,7 +157,7 @@ public Call find() { * a * Single Custom Field * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -164,7 +201,7 @@ public Call fetch() { * a custom * field * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -205,7 +242,7 @@ public Call uploadCustomField(Map body) { * a custom * field * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -222,7 +259,7 @@ public Call uploadCustomFieldUsingJSONObject(JSONObject body) { * a custom * field * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call update(JSONObject body) { @@ -240,7 +277,7 @@ public Call update(JSONObject body) { * a custom * field * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call delete() { diff --git a/src/main/java/com/contentstack/cms/stack/Folder.java b/src/main/java/com/contentstack/cms/stack/Folder.java index a24010fd..b8211427 100644 --- a/src/main/java/com/contentstack/cms/stack/Folder.java +++ b/src/main/java/com/contentstack/cms/stack/Folder.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -10,7 +11,7 @@ import java.util.HashMap; import java.util.Map; -public class Folder { +public class Folder implements BaseImplementation { protected final Map headers; protected Map params; @@ -36,34 +37,64 @@ void validate() { } /** - * Sets header for the request - * - * @param key - * header key for the request - * @param value - * header value for the request + * @param key A string representing the key of the parameter. It cannot be + * null and must be + * provided as a non-null value. + * @param value The "value" parameter is of type Object, which means it can + * accept any type of + * object as its value. + * @return instance of the {@link Folder} + */ + @Override + public Folder addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of the {@link Folder} */ - public void addHeader(@NotNull String key, @NotNull Object value) { + @Override + public Folder addHeader(@NotNull String key, @NotNull String value) { this.headers.put(key, value); + return this; } /** - * Sets header for the request - * - * @param key - * query param key for the request - * @param value - * query param value for the request + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of the {@link Folder} */ - public void addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); + @Override + public Folder addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of the {@link Folder} + */ + @Override + public Folder addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; } /** * Set header for the request * - * @param key - * Removes query param using key of request + * @param key Removes query param using key of request */ public void removeParam(@NotNull String key) { this.params.remove(key); @@ -94,9 +125,9 @@ protected void clearParams() { * * @return Call * @see - * Unpublish An Asset - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#unpublish-an-asset"> + * Unpublish An Asset + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query params * @since 0.1.0 */ @@ -125,14 +156,13 @@ public Call fetch() { * is 5. When nesting folder, you * cannot nest a folder within the same folder or within its child folders * - * @param requestBody - * JSONObject request body + * @param requestBody JSONObject request body * @return Call * @see - * Create - * a folder - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#create-a-folder"> + * Create + * a folder + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query params * @since 0.1.0 */ @@ -159,13 +189,12 @@ public Call create(@Nullable JSONObject requestBody) { * is 5. When nesting folder, you * cannot nest a folder within the same folder or within its child folders. * - * @param requestBody - * JSONObject request body { "asset": { "name": "Demo" } } + * @param requestBody JSONObject request body { "asset": { "name": "Demo" } } * @return Call * @see - * Update ORr Move Folder - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#update-or-move-folder"> + * Update ORr Move Folder + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query params * @since 0.1.0 */ @@ -182,10 +211,10 @@ public Call update(@Nullable JSONObject requestBody) { * * @return Call * @see - * Delete - * A Folder - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#delete-a-folder"> + * Delete + * A Folder + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query params * @since 0.1.0 */ diff --git a/src/main/java/com/contentstack/cms/stack/GlobalField.java b/src/main/java/com/contentstack/cms/stack/GlobalField.java index f8702490..28d6924b 100644 --- a/src/main/java/com/contentstack/cms/stack/GlobalField.java +++ b/src/main/java/com/contentstack/cms/stack/GlobalField.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; @@ -26,7 +27,7 @@ * @version v0.1.0 * @since 2022-10-22 */ -public class GlobalField { +public class GlobalField implements BaseImplementation { /** * The Service. @@ -54,27 +55,59 @@ void validate() { throw new IllegalAccessError("Global Field Uid can not be null or empty"); } + /** - * Sets header for the request - * - * @param key header key for the request - * @param value header value for the request - * @return instance of {@link GlobalField} + * @param key A string representing the key of the parameter. It cannot be + * null and must be + * provided as a non-null value. + * @param value The "value" parameter is of type Object, which means it can + * accept any type of + * object as its value. + * @return instance of the GlobalField object + */ + @Override + public GlobalField addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of the GlobalField object */ - public GlobalField addHeader(@NotNull String key, @NotNull Object value) { + @Override + public GlobalField addHeader(@NotNull String key, @NotNull String value) { this.headers.put(key, value); return this; } /** - * Sets header for the request - * - * @param key query param key for the request - * @param value query param value for the request - * @return instance of {@link GlobalField} + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of the GlobalField object */ - public GlobalField addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); + @Override + public GlobalField addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of the GlobalField object + */ + @Override + public GlobalField addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); return this; } @@ -91,6 +124,7 @@ public GlobalField removeParam(@NotNull String key) { /** * To clear all the query params + * * @return instance of {@link GlobalField} */ protected GlobalField clearParams() { diff --git a/src/main/java/com/contentstack/cms/stack/Label.java b/src/main/java/com/contentstack/cms/stack/Label.java index d6e0445a..364e9ae9 100644 --- a/src/main/java/com/contentstack/cms/stack/Label.java +++ b/src/main/java/com/contentstack/cms/stack/Label.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; @@ -24,7 +25,7 @@ * @version v0.1.0 * @since 2022-10-22 */ -public class Label { +public class Label implements BaseImplementation

* You can now pass the branch header in the API request to fetch or manage @@ -29,7 +30,7 @@ * @version v0.1.0 * @since 2022-10-22 */ -public class Release { +public class Release implements BaseImplementation { protected final ReleaseService service; protected HashMap headers; @@ -57,24 +58,60 @@ void validate() { throw new IllegalAccessError("Release Uid Can Not Be Null OR Empty"); } + /** - * Sets header for the request - * - * @param key header key for the request - * @param value header value for the request + * @param key A string representing the key of the parameter. It cannot be + * null and must be + * provided as a non-null value. + * @param value The "value" parameter is of type Object, which means it can + * accept any type of + * object as its value. + * @return instance of {@link @Release} + */ + @Override + public Release addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of {@link @Release} */ - public void addHeader(@NotNull String key, @NotNull Object value) { + @Override + public Release addHeader(@NotNull String key, @NotNull String value) { this.headers.put(key, value); + return this; } /** - * Sets header for the request - * - * @param key query param key for the request - * @param value query param value for the request + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of {@link @Release} */ - public void addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); + @Override + public Release addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of {@link @Release} + */ + @Override + public Release addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; } /** @@ -103,7 +140,7 @@ protected void clearParams() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-all-releases">Get * all Releases * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -123,7 +160,7 @@ public Call find() { * a singel * release * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -145,7 +182,7 @@ public Call fetch() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#create-a-release">Create * a release * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -167,7 +204,7 @@ public Call create(@NotNull JSONObject requestBody) { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#update-a-release">Update * a release * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -187,7 +224,7 @@ public Call update(@NotNull JSONObject requestBody) { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#delete-a-release">Delete * a release * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -211,7 +248,7 @@ public Call delete() { * a * release item * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -237,7 +274,7 @@ public ReleaseItem item() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#deploy-a-release">Deploy * a release * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -260,7 +297,7 @@ public Call deploy(@NotNull JSONObject requestBody) { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#clone-a-release">Clone * all release * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call clone(@NotNull JSONObject requestBody) { diff --git a/src/main/java/com/contentstack/cms/stack/ReleaseItem.java b/src/main/java/com/contentstack/cms/stack/ReleaseItem.java index fd0d5818..512ee834 100644 --- a/src/main/java/com/contentstack/cms/stack/ReleaseItem.java +++ b/src/main/java/com/contentstack/cms/stack/ReleaseItem.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; @@ -21,7 +22,7 @@ * @version v0.1.0 * @since 2022-10-22 */ -public class ReleaseItem { +public class ReleaseItem implements BaseImplementation { protected final ReleaseService service; protected HashMap headers; @@ -40,24 +41,60 @@ void validate() { throw new IllegalAccessError("Release Uid can not be null or empty"); } + /** - * Sets header for the request - * - * @param key header key for the request - * @param value header value for the request + * @param key A string representing the key of the parameter. It cannot be + * null and must be + * provided as a non-null value. + * @param value The "value" parameter is of type Object, which means it can + * accept any type of + * object as its value. + * @return instance of {@link ReleaseItem} + */ + @Override + public ReleaseItem addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of {@link ReleaseItem} */ - public void addHeader(@NotNull String key, @NotNull Object value) { + @Override + public ReleaseItem addHeader(@NotNull String key, @NotNull String value) { this.headers.put(key, value); + return this; } /** - * Sets header for the request - * - * @param key query param key for the request - * @param value query param value for the request + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of {@link ReleaseItem} */ - public void addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); + @Override + public ReleaseItem addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of {@link ReleaseItem} + */ + @Override + public ReleaseItem addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; } /** @@ -98,7 +135,7 @@ public Call find() { * 'Body' section, you need to provide * the details of the item such as the UID, version (of the entry), content type * UID (of an entry), the action to be - * performed (publish/unpublish), and the locale of the item. + * performed (publish/un-publish), and the locale of the item. * * @param jsonBody requestBody for create/add single Item * @return Call @@ -116,7 +153,7 @@ public Call create(@NotNull JSONObject jsonBody) { * 'Body' section, you need to provide * the details of the items such as their UIDs, versions (in case of entries), * content type UIDs (in case of - * entries), the action to be performed (publish/unpublish), and the locales of + * entries), the action to be performed (publish/un-publish), and the locales of * the items. * * @param jsonBody requestBody for create/add single Item @@ -184,7 +221,7 @@ public Call update(@NotNull JSONObject jsonBody) { * section, you need to provide the * details of the item such as their UIDs, version (of the entry), content type * UID (of an entry), the action to be - * performed (publish/unpublish), and the locale of the item. + * performed (publish/un-publish), and the locale of the item. *

* OR *

@@ -196,7 +233,7 @@ public Call update(@NotNull JSONObject jsonBody) { * section, you need to provide the UIDs * of the items along with details such as their locale, versions, the action to * be performed on the items - * (publish/unpublish), and content type UID of entries (if any). + * (publish/un-publish), and content type UID of entries (if any). * * @return Call */ diff --git a/src/main/java/com/contentstack/cms/stack/Roles.java b/src/main/java/com/contentstack/cms/stack/Roles.java index 66c55a33..c22d6f71 100644 --- a/src/main/java/com/contentstack/cms/stack/Roles.java +++ b/src/main/java/com/contentstack/cms/stack/Roles.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; @@ -16,12 +17,11 @@ * @author ***REMOVED*** * @version v0.1.0 * @see Roles - * - * + * "https://www.contentstack.com/docs/developers/invite-users-and-assign-roles/about-stack-roles">Roles + * * @since 2022-10-22 */ -public class Roles { +public class Roles implements BaseImplementation { protected final RolesService service; protected HashMap headers; @@ -46,35 +46,11 @@ void validate() { throw new IllegalAccessError("Role uid can not be null or empty"); } - /** - * Sets header for the request - * - * @param key - * header key for the request - * @param value - * header value for the request - */ - public void addHeader(@NotNull String key, @NotNull Object value) { - this.headers.put(key, value); - } - - /** - * Sets header for the request - * - * @param key - * query param key for the request - * @param value - * query param value for the request - */ - public void addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); - } /** * Set header for the request * - * @param key - * Removes query param using key of request + * @param key Removes query param using key of request */ public void removeParam(@NotNull String key) { this.params.remove(key); @@ -100,11 +76,11 @@ protected void clearParams() { * * @return Call * @see Get - * all - * Roles - * - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-all-roles">Get + * all + * Roles + * + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -118,11 +94,11 @@ public Call find() { * * @return Call * @see Get - * a - * single Roles - * - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-a-single-role">Get + * a + * single Roles + * + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -140,15 +116,14 @@ public Call fetch() { * permissions (which include the details of * the content types, environments, and languages that are accessible). * - * @param requestBody - * details of the delivery role in @{@link JSONObject} format + * @param requestBody details of the delivery role in @{@link JSONObject} format * @return Call * @see Create - * a - * Roles - * - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#create-a-role">Create + * a + * Roles + * + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call create(@NotNull JSONObject requestBody) { @@ -167,14 +142,13 @@ public Call create(@NotNull JSONObject requestBody) { * (which include the details of the content types, environments, and languages * that are accessible). * - * @param requestBody - * the body should be of @{@link JSONObject} type + * @param requestBody the body should be of @{@link JSONObject} type * @return Call * @see Update - * Role - * - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#update-role">Update + * Role + * + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -188,11 +162,11 @@ public Call update(@NotNull JSONObject requestBody) { * * @return Call * @see Delete - * a - * Role - * - * @see #addHeader(String, Object) to add headers + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#delete-role">Delete + * a + * Role + * + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call delete() { @@ -200,4 +174,27 @@ public Call delete() { return this.service.deleteRole(this.headers, this.roleUid); } + @Override + public Roles addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + @Override + public Roles addHeader(@NotNull String key, @NotNull String value) { + this.headers.put(key, value); + return this; + } + + @Override + public Roles addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + @Override + public Roles addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; + } } diff --git a/src/main/java/com/contentstack/cms/stack/Stack.java b/src/main/java/com/contentstack/cms/stack/Stack.java index e729a32f..97b38548 100644 --- a/src/main/java/com/contentstack/cms/stack/Stack.java +++ b/src/main/java/com/contentstack/cms/stack/Stack.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; @@ -16,9 +17,9 @@ * * @author ***REMOVED*** * @version v0.1.0 - * @since 2022-10-22 + * @since 2022 -10-22 */ -public class Stack { +public class Stack implements BaseImplementation { private final StackService service; protected Retrofit client; @@ -37,24 +38,6 @@ public Stack(@NotNull Retrofit client) { this.service = client.create(StackService.class); } - /** - * The addHeader function adds a key-value pair to the headers map and returns - * the updated Stack - * object. - * - * @param key A string representing the key of the header. It is marked - * as @NotNull, which means it - * cannot be null and must be provided. - * @param value The value parameter is of type Object, which means it can accept - * any type of object as - * its value. - * @return The method is returning a Stack object. - */ - public Stack addHeader(@NotNull String key, @NotNull Object value) { - this.headers.put(key, value); - return this; - } - /** * The addParam function adds a key-value pair to a stack and returns the * updated stack. @@ -71,6 +54,46 @@ public Stack addParam(@NotNull String key, @NotNull Object value) { return this; } + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of the Stack + */ + @Override + public Stack addHeader(@NotNull String key, @NotNull String value) { + this.headers.put(key, value); + return this; + } + + /** + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of the Stack + */ + @Override + public Stack addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of the Stack + */ + @Override + public Stack addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; + } + /** * The function removes a parameter from a stack and returns the modified stack. * @@ -546,10 +569,10 @@ public Roles roles(String roleUid) { } /** - * You can pin a set of entries and assets (along with the deploy action, i.e., - * publish/unpublish) to a + * You can pin a set of entries and assets (along with the deployment action, i.e., + * publish/un-publish) to a * release, and then deploy this release to an environment. This will - * publish/unpublish all the items of the release to the specified environment. + * publish/un-publish all the items of the release to the specified environment. * Read more about Releases. * * @return Release @@ -559,10 +582,10 @@ public Release releases() { } /** - * You can pin a set of entries and assets (along with the deploy action, i.e., - * publish/unpublish) to a + * You can pin a set of entries and assets (along with the deployment action, i.e., + * publish/un-publish) to a * release, and then deploy this release to an environment. This will - * publish/unpublish all the items of the release to the specified environment. + * publish/un-publish all the items of the release to the specified environment. * Read more about Releases. * * @param releaseUid The unique ID of the release of which you want to retrieve @@ -631,7 +654,7 @@ public AuditLog auditLog(@NotNull String logItemUid) { /** * The Publishing Queue displays the historical and current details of - * activities such as publish, unpublish, or + * activities such as publish, un-publish, or * delete that can be performed on entries and/or assets. It also shows details * of Release deployments. These * details include time, entry, content type, version, language, user, @@ -649,7 +672,7 @@ public PublishQueue publishQueue() { } /** - * You can perform bulk operations such as Publish, Unpublish, and Delete on + * You can perform bulk operations such as Publish, Un-publish, and Delete on * multiple entries or assets, or Change * the Workflow Details of multiple entries or assets at the same time. *

@@ -666,7 +689,7 @@ public BulkOperation bulkOperation() { /** * The Publishing Queue displays the historical and current details of - * activities such as publish, unpublish, or + * activities such as publish, un-publish, or * delete that can be performed on entries and/or assets. It also shows details * of Release deployments. These * details include time, entry, content type, version, language, user, @@ -798,6 +821,45 @@ public Alias alias(String aliasUid) { return new Alias(this.client, aliasUid); } + + /** + * The taxonomy works on data where hierarchy is configured + * + *

+     * {@code
+     * Stack stack = new Contentstack.Builder().setAuthtoken("authtoken").build().stack();
+     * Taxonomy taxonomy = stack.taxonomy();
+     * Call response = taxonomy.fetch().execute();
+     * }
+     * 
+ * + * @return instance of Taxonomy + */ + public Taxonomy taxonomy() { + return new Taxonomy(this.client, this.headers); + } + + + /** + * The taxonomy works on data where hierarchy is configured + * + * @param taxonomyUid the taxonomy uid + * @return instance of Taxonomy + *

+ *
+     * {@code
+     * Stack stack = new Contentstack.Builder().setAuthtoken("authtoken").build().stack();
+     * Taxonomy taxonomy = stack.taxonomy("taxonomyId");
+     * taxonomy.terms();
+     * taxonomy.fetch();
+     * taxonomy.find();
+     * }
+     * 
+ */ + public Taxonomy taxonomy(@NotNull String taxonomyUid) { + return new Taxonomy(this.client, this.headers, taxonomyUid); + } + /** * Get stacks *
@@ -826,7 +888,7 @@ public Alias alias(String aliasUid) { * UID in the header. *
*
- * - Add headers using {@link #addHeader(String, Object)} + * - Add headers using {@link #addHeader(String, String)} *
* - Add query parameters using {@link #addParam(String, Object)} * @@ -945,7 +1007,7 @@ public Call transferOwnership(@NotNull JSONObject body) { *
    *
  • To Accept stack owned by other user, user * {@link #addParam(String, Object)} to add query parameters
  • - *
  • To Add Header use {@link #addHeader(String, Object)}
  • + *
  • To Add Header use {@link #addHeader(String, String)}
  • *
* * @param ownershipToken the ownership token received via email by another user. @@ -1116,16 +1178,16 @@ public Call share(@NotNull JSONObject requestBody) { } /** - * Unshare a stack + * Un-share a stack *

- * The Unshare a stack removes the user account from the list of collaborators. + * The Un-share a stack removes the user account from the list of collaborators. * Once this call is executed, the user * will not be able to view the stack in their account. *

*
*

* In the 'Body' section, you need to provide the email ID of the user from whom - * you wish to unshare the stack. + * you wish to un-share the stack. *

* Here is a sample of the Request Body: * @@ -1190,4 +1252,5 @@ public Call roles(@NotNull JSONObject body) { return service.updateUserRoles(this.headers, body); } + } diff --git a/src/main/java/com/contentstack/cms/stack/Taxonomy.java b/src/main/java/com/contentstack/cms/stack/Taxonomy.java new file mode 100644 index 00000000..ca991101 --- /dev/null +++ b/src/main/java/com/contentstack/cms/stack/Taxonomy.java @@ -0,0 +1,254 @@ +package com.contentstack.cms.stack; + +import com.contentstack.cms.BaseImplementation; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; +import org.json.simple.JSONObject; +import retrofit2.Call; +import retrofit2.Retrofit; + +import java.util.HashMap; + + +public class Taxonomy implements BaseImplementation { + + private String taxonomyId; + final TaxonomyService taxonomyService; + protected HashMap headers; + protected HashMap params; + + + protected Taxonomy(Retrofit client, HashMap headers) { + this.headers = new HashMap<>(); + this.params = new HashMap<>(); + this.headers.putAll(headers); + this.taxonomyService = client.create(TaxonomyService.class); + } + + protected Taxonomy(Retrofit client, HashMap headers, @NotNull String taxonomyId) { + this.headers = new HashMap<>(); + this.params = new HashMap<>(); + this.taxonomyId = taxonomyId; + this.headers.putAll(headers); + this.taxonomyService = client.create(TaxonomyService.class); + } + + /** + * @param key A string representing the key of the parameter. It cannot be + * null and must be + * provided as a non-null value. + * @param value The "value" parameter is of type Object, which means it can + * accept any type of + * object as its value. + * @return instance of Taxonomy + */ + @Override + public Taxonomy addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of Taxonomy + */ + @Override + public Taxonomy addHeader(@NotNull String key, @NotNull String value) { + this.headers.put(key, value); + return this; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of Taxonomy + */ + @Override + public Taxonomy addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; + } + + /** + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of Taxonomy + */ + @Override + public Taxonomy addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + /** + * Get all taxonomies call. + *

+ * Query parameters: + *

    + *
  • + * include_terms_count: Whether to include the count of terms in the response. Values: true or false. + *
  • + *
  • + * include_referenced_terms_count: Whether to include the count of referenced terms in the response. Values: true or false. + *
  • + *
  • + * include_referenced_entries_count: Whether to include the count of referenced entries in the response. Values: true or false. + *
  • + *
  • + * include_count: Whether to include the total count of taxonomies in the response. Values: true or false. + *
  • + *
  • + * asc or desc: Sort order based on the UID field. Values: field_uid (replace with the actual field UID), or omit for no sorting. + *
  • + *
  • + * typeahead: Search string for typeahead functionality. Values: Any search string. + *
  • + *
  • + * include_deleted: Whether to include deleted taxonomies in the response. Values: true or false. + *
  • + *
  • + * skip: Number of records to skip in pagination. Values: Any non-negative integer. + *
  • + *
  • + * limit: Maximum number of records to return. Values: Any positive integer. + *
  • + *
+ * + * @return the call + * + *

+ * Example + *
+     *     {@code
+     *     Response response = taxonomy.find().execute();
+     *     }
+     * 
+ */ + public Call find() { + return this.taxonomyService.find(this.headers, this.params); + } + + /** + * Fetch Single Taxonomy Call. + *

+ * Query Parameters: + *

    + *
  • + * include_terms_count: Whether to include the count of terms in the response. Values: true or false. + *
  • + *
  • + * include_referenced_terms_count: Whether to include the count of terms referred in at least 1 entry. Values: true or false. + *
  • + *
  • + * include_referenced_entries_count: Whether to include the count of entries where at least 1 term of this taxonomy is referred. Values: true or false + *
  • + *
+ * + * @param taxonomyId the taxonomy id + * @return the call + *

+ * Example + *
+     *     {@code
+     *     Response response = taxonomy.fetch("taxonomyId").execute();
+     *     }
+     * 
+ */ + public Call fetch(@NotNull String taxonomyId) { + return this.taxonomyService.fetch(this.headers, taxonomyId, this.params); + } + + /** + * Create Taxonomy call. + * + * @param body the body + * @return the call + * + *

+ * Example + *
+     *     {@code
+     *     JSONObject body = new JSONObject
+     *     Response response = taxonomy.create(body).execute();
+     *     }
+     * 
+ */ + public Call create(@NotNull JSONObject body) { + return this.taxonomyService.create(this.headers, body); + } + + /** + * Update Taxonomy call. + * + * @param taxonomyId - The taxonomy for which we need to update the details + * @param body the body + * @return the call + * + *

+ * Example + *
+     *     {@code
+     *     JSONObject body = new JSONObject();
+     *     JSONObject bodyContent = new JSONObject();
+     *     bodyContent.put("name", "Taxonomy 1");
+     *     bodyContent.put("description", "Description updated for Taxonomy 1);
+     *     body.put("taxonomy", bodyContent);
+     *     Response response = taxonomy.update("taxonomyId", body).execute();
+     *     }
+     * 
+ */ + public Call update(@NotNull String taxonomyId, @NotNull JSONObject body) { + return this.taxonomyService.update(this.headers, taxonomyId, body); + } + + /** + * Delete Taxonomy call. + * + * @param taxonomyId - The taxonomy for which we need to update the details + * @return the call + *

+ * Example + *
+     *     {@code
+     *     Response response = taxonomy.delete("taxonomyId").execute();
+     *     }
+     * 
+ */ + public Call delete(@NotNull String taxonomyId) { + return this.taxonomyService.delete(this.headers, taxonomyId); + } + + /** + * Clear params for internal uses only for testing + */ + protected void clearParams() { + this.params.clear(); + } + + + /** + * Get terms information + *

+ *

Examples

+ *
+     *     {@code
+     *     Term terms = stack("authtoken").taxonomy("taxonomyId").term();
+     *     }
+     * 
+ * + * @return instance of {@link Terms} + */ + public Terms terms() { + return new Terms(this.taxonomyService, this.headers, this.taxonomyId); + } + + +} diff --git a/src/main/java/com/contentstack/cms/stack/TaxonomyService.java b/src/main/java/com/contentstack/cms/stack/TaxonomyService.java new file mode 100644 index 00000000..d000927b --- /dev/null +++ b/src/main/java/com/contentstack/cms/stack/TaxonomyService.java @@ -0,0 +1,50 @@ +package com.contentstack.cms.stack; + +import okhttp3.ResponseBody; +import org.json.simple.JSONObject; +import retrofit2.Call; +import retrofit2.http.*; + +import java.util.HashMap; +import java.util.Map; + +public interface TaxonomyService { + + @GET("taxonomies") + Call find(@HeaderMap Map headers, @QueryMap Map params); + + @GET("taxonomies/{taxonomy_uid}") + Call fetch(@HeaderMap Map headers, @Path("taxonomy_uid") String uid, @QueryMap Map query); + + @GET("taxonomies") + Call create(@HeaderMap Map headers, @Body JSONObject body); + + @PUT("taxonomies/{taxonomy_uid}") + Call update(@HeaderMap Map headers, @Path("taxonomy_uid") String uid, @Body JSONObject body); + + @DELETE("taxonomies/{taxonomy_uid}") + Call delete(@HeaderMap Map headers, @Path("taxonomy_uid") String uid); + + //--Terms-- + @POST("taxonomies/{taxonomy_uid}/terms") + Call createTerm(@HeaderMap HashMap headers, @Path("taxonomy_uid") String taxonomyId, @Body JSONObject body); + + @GET("taxonomies/{taxonomy_uid}/terms") + Call findTerm(@HeaderMap HashMap headers, @Path("taxonomy_uid") String taxonomyId, @QueryMap Map queryParams); + + @GET("taxonomies/{taxonomy_uid}/terms/{term_id}") + Call fetchTerm(@HeaderMap HashMap headers, @Path("taxonomy_uid") String taxonomyId, @Path("term_id") String termId, @QueryMap Map queryParams); + + @GET("taxonomies/{taxonomy_uid}/terms/{term_id}/descendants") + Call descendantsTerm(@HeaderMap HashMap headers, @Path("taxonomy_uid") String taxonomyId, @Path("term_id") String termId, @QueryMap Map queryParams); + + @GET("taxonomies/{taxonomy_uid}/terms/{term_id}/ancestors") + Call ancestorsTerm(@HeaderMap HashMap headers, @Path("taxonomy_uid") String taxonomyId, @Path("term_id") String termId, @QueryMap Map queryParams); + + @PUT("taxonomies/{taxonomy_uid}/terms/{term_id}") + Call updateTerm(@HeaderMap HashMap headers, @Path("taxonomy_uid") String taxonomyId, @Path("term_id") String termId, @Body JSONObject body); + + @GET("taxonomies/all/terms?term={term_string}") + Call searchTerm(@HeaderMap HashMap headers, @Path("term_string") String termString); + +} diff --git a/src/main/java/com/contentstack/cms/stack/Terms.java b/src/main/java/com/contentstack/cms/stack/Terms.java new file mode 100644 index 00000000..e7e231eb --- /dev/null +++ b/src/main/java/com/contentstack/cms/stack/Terms.java @@ -0,0 +1,277 @@ +package com.contentstack.cms.stack; + +import com.contentstack.cms.BaseImplementation; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; +import org.json.simple.JSONObject; +import retrofit2.Call; + +import java.util.HashMap; + +/** + * @author @***REMOVED*** + * @since 1.1.0 + * provides an implementation + *
    + *
  • Create a Term
  • + *
  • Get all Terms of a Taxonomy
  • + *
  • Get a single Term
  • + *
  • Get descendants of a single Term
  • + *
  • Get ancestors of a single Term
  • + *
  • Update a Term
  • + *
  • Search for all Terms within all Taxonomies
  • + *
+ */ +public class Terms implements BaseImplementation { + + final TaxonomyService taxonomyService; + final HashMap headers; + final String taxonomyId; + final HashMap params; + + + public Terms(@NotNull TaxonomyService service, HashMap headers, String taxonomyId) { + this.taxonomyService = service; + this.headers = headers; + this.taxonomyId = taxonomyId; + this.params = new HashMap<>(); + } + + + /** + * @param key A string representing the key of the parameter. It cannot be + * null and must be + * provided as a non-null value. + * @param value The "value" parameter is of type Object, which means it can + * accept any type of + * object as its value. + * @return instance of Terms + */ + @Override + public Terms addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of Terms + */ + @Override + public Terms addHeader(@NotNull String key, @NotNull String value) { + this.headers.put(key, value); + return this; + } + + /** + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of Terms + */ + @Override + public Terms addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of Terms + */ + @Override + public Terms addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; + } + + + /** + * Create Terms call. + * + * @param body The JSONObject request body + * @return instance of Call + * + *

+ * Example + *
+     *     {@code
+     *     Stack stack = new Contentstack.Builder().build().stack(headers);
+     *     JSONObject body = new JSONObject();
+     *     Term term = stack.taxonomy("taxonomyId").terms().create(body);
+     *     }
+     * 
+ */ + public Call create(@NotNull JSONObject body) { + return this.taxonomyService.createTerm(this.headers, taxonomyId, body); + } + + /** + *

+ * Supported Query Parameters: to use query parameters use #addParams("key", "value"); + *

    + *
  • + * taxonomy_uid - The taxonomy for which we need the terms + *
  • + *
  • + * depth - Include the terms upto the depth specified if set to a number greater than 0, include all the terms if set to 0, default depth will be set to 1 + *
  • + *
  • + * include_children_count - Include count of number of children under each term if set to true + *
  • + *
  • + * include_referenced_entries_count - Include count of the entries where this term is referred + *
  • + *
  • + * include_count - Include count of the documents/nodes that matched the query + *
  • + *
  • + * asc|desc - Sort the given field in either ascending or descending order + *
  • + *
  • + * typeahead - Used to match the given string in all terms and return the matched result + *
  • + *
  • + * deleted - Used to fetch only the deleted terms + *
  • + *
  • + * skip - Skip the number of documents/nodes + *
  • + *
  • + * limit - Limit the result to number of documents/nodes + *
  • + *
+ *

+ * Example + *
+     *     {@code
+     *     Stack stack = new Contentstack.Builder().build().stack(headers);
+     *     Term term = stack.taxonomy("taxonomyId").terms().addParam("limit", 2).find();
+     *     }
+     * 
+ * + * @see #addParam(String, Object) to add query parameters + * returns instance of Call + */ + public Call find() { + return this.taxonomyService.findTerm(this.headers, taxonomyId, this.params); + } + + /** + * Fetch single term based on term uid. + * + * @param termUid The term for which we need the details + * @return instance of call

Supported Query Parameters: to use query parameters use #addParams("key", "value");

  • include_children_count - Include count of number of children under each term
  • include_referenced_entries_count - Include count of the entries where this term is referred

Example
     {@code
+     *     Stack stack = new Contentstack.Builder().build().stack(headers);
+     *     Term term = stack.taxonomy("taxonomyId").terms().find();
+     *     } 
+ * @see #addParam(String, Object) #addParam(String, Object)to add query parameters + */ + public Call fetch(@NotNull String termUid) { + return this.taxonomyService.fetchTerm(this.headers, taxonomyId, termUid, this.params); + } + + /** + * Get descendants of a single Term + * + * @param termUid The term for which we need the details + * @return The details of the term descendants + *

+ * URL/Query parameters + *

    + *
  • + * depth - Include the terms upto the depth specified if set to a number greater than 0, include all the terms if set to 0, default depth will be set to 1 + *
  • + * include_children_count - Include count of number of children under each term + *
  • + * include_referenced_entries_count - Include count of the entries where atleast 1 term of this taxonomy is referred + *
  • + * include_count - Include count of the documents/nodes that matched the query + *
  • + * skip - Skip the number of documents/nodes + *
  • + * limit - Limit the result to number of documents/nodes + *
  • + *
+ *

Example

 {@code
+     *      Stack stack = new Contentstack.Builder().build().stack(headers);
+     *      Term term = stack.taxonomy("taxonomyId").terms().descendants("termId").;
+     *     }
+     *     
+ *

+ */ + public Call descendants(@NotNull String termUid) { + return this.taxonomyService.descendantsTerm(this.headers, this.taxonomyId, termUid, this.params); + } + + /** + * Get ancestors of a single Term + * + * @param termUid The term for which we need the details + * @return The details of the term ancestors + *

Example

 {@code
+     *      Stack stack = new Contentstack.Builder().build().stack(headers);
+     *      Term term = stack.taxonomy("taxonomyId").terms().ancestors("termId");
+     *     }
+     *     
+ * + */ + public Call ancestors(@NotNull String termUid) { + return this.taxonomyService.ancestorsTerm(this.headers, this.taxonomyId, termUid, this.params); + } + + /** + * @param termUid - The term for which we need the details + * @param body the JSONObject body for the request + * @return instance of Call + *

+ * Example + *

+     * {@code
+     *   body = new RequestBody{
+     *   "term": {
+     *     "name": "Term 1",
+     *     "description": "Term 1 Description updated for Taxonomy 1"
+     *   }
+     * }
+     *
+     * Stack stack = new Contentstack.Builder().build().stack(headers);
+     * Term term = stack.taxonomy("taxonomyId").terms().update(body);
+     * }
+     * 
+ */ + public Call update(@NotNull String termUid, @NotNull JSONObject body) { + return this.taxonomyService.updateTerm(this.headers, this.taxonomyId, termUid, body); + } + + + /** + * @param termString: The string for which we need to search for the matching terms, should either match with term uid or term name + * @return instance of Call + *

+ + * Example + *

+     * {@code
+     * Stack stack = new Contentstack.Builder().build().stack(headers);
+     * Term term = stack.taxonomy("taxonomyId").terms().search("search anything");
+     * }
+     * 
+ */ + public Call search(@NotNull String termString) { + return this.taxonomyService.searchTerm(this.headers, termString); + } + + + protected void clearParams() { + this.params.clear(); + } +} diff --git a/src/main/java/com/contentstack/cms/stack/Webhook.java b/src/main/java/com/contentstack/cms/stack/Webhook.java index 9fef5806..aaec728e 100644 --- a/src/main/java/com/contentstack/cms/stack/Webhook.java +++ b/src/main/java/com/contentstack/cms/stack/Webhook.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import com.contentstack.cms.core.Util; import okhttp3.MediaType; import okhttp3.MultipartBody; @@ -30,7 +31,7 @@ * * @since 2022-10-22 */ -public class Webhook { +public class Webhook implements BaseImplementation { protected final WebhookService service; protected HashMap headers; @@ -55,24 +56,60 @@ void validate() { throw new IllegalAccessError("Webhook uid can not be null or empty"); } + /** - * Sets header for the request - * - * @param key header key for the request - * @param value header value for the request + * @param key A string representing the key of the parameter. It cannot be + * null and must be + * provided as a non-null value. + * @param value The "value" parameter is of type Object, which means it can + * accept any type of + * object as its value. + * @return instance of {@link Webhook} + */ + @Override + public Webhook addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of {@link Webhook} */ - public void addHeader(@NotNull String key, @NotNull Object value) { + @Override + public Webhook addHeader(@NotNull String key, @NotNull String value) { this.headers.put(key, value); + return this; } /** - * Sets header for the request - * - * @param key query param key for the request - * @param value query param value for the request + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of {@link Webhook} */ - public void addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); + @Override + public Webhook addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of {@link Webhook} + */ + @Override + public Webhook addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; } /** @@ -105,7 +142,7 @@ protected void clearParams() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-all-webhooks">Get * all Webhooks * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -171,7 +208,7 @@ public Call fetch() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#create-a-webhook">Create * a Webhook * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call create(@NotNull JSONObject requestBody) { @@ -220,7 +257,7 @@ public Call create(@NotNull JSONObject requestBody) { * a * Webhook * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call update(JSONObject requestBody) { @@ -240,7 +277,7 @@ public Call update(JSONObject requestBody) { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#delete-webhook">Delete * Webhook * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -261,7 +298,7 @@ public Call delete() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#export-a-webhook">Export * Webhook * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -283,7 +320,7 @@ public Call export() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#import-a-webhook">Import * Webhook * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -304,7 +341,7 @@ public Call importWebhook(@NotNull String fileName, @NotNull Strin * "https://www.contentstack.com/docs/developers/apis/content-management-api/#import-an-existing-webhook">Import * Existing * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call importExisting() { @@ -370,7 +407,7 @@ public Call importExisting() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-executions-of-a-webhook">Get * executions of a webhook * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -397,7 +434,7 @@ public Call getExecutions() { * a * Webhook * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call retry(@NotNull String executionUid) { @@ -422,7 +459,7 @@ public Call retry(@NotNull String executionUid) { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-latest-execution-log-of-a-webhook">Get * latest execution log of a webhook * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call getExecutionLog(@NotNull String executionUid) { diff --git a/src/main/java/com/contentstack/cms/stack/Workflow.java b/src/main/java/com/contentstack/cms/stack/Workflow.java index 180652ca..4bd27c5f 100644 --- a/src/main/java/com/contentstack/cms/stack/Workflow.java +++ b/src/main/java/com/contentstack/cms/stack/Workflow.java @@ -1,5 +1,6 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; @@ -16,7 +17,6 @@ *

* Read more about Workflow - * * Note: You cannot create workflows in a stack that supports branches * when using the classic Contentstack interface. * @@ -24,7 +24,7 @@ * @version v01.0 * @since 2022-10-22 */ -public class Workflow { +public class Workflow implements BaseImplementation { protected final WorkflowService service; protected HashMap headers; @@ -49,25 +49,6 @@ void validate() { throw new IllegalAccessError("Workflow uid can not be null or empty"); } - /** - * Sets header for the request - * - * @param key header key for the request - * @param value header value for the request - */ - public void addHeader(@NotNull String key, @NotNull Object value) { - this.headers.put(key, value); - } - - /** - * Sets header for the request - * - * @param key query param key for the request - * @param value query param value for the request - */ - public void addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); - } /** * Set header for the request @@ -94,7 +75,7 @@ protected void clearParams() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-all-workflows">Get * all workflow * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call find() { @@ -111,7 +92,7 @@ public Call find() { * a singel * workflow * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call fetch() { @@ -139,7 +120,6 @@ public Call fetch() { * To control who can edit an entry at different stages of the workflow, you can * pass the entry_lock parameter * inside each workflow stage. - * * Note: Workflow superusers, organization owners, and stack * owners/admins can edit or delete the entry in any workflow stage, * irrespective of the stage access rules set for @@ -154,7 +134,7 @@ public Call fetch() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#create-a-workflow">Create * workflow * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call create(@NotNull JSONObject requestBody) { @@ -193,7 +173,7 @@ public Call create(@NotNull JSONObject requestBody) { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#add-or-update-workflow-details">Update * Workflow * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call update(@NotNull JSONObject requestBody) { @@ -208,9 +188,8 @@ public Call update(@NotNull JSONObject requestBody) { * @see Disable * workflow - * * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call disable() { @@ -226,7 +205,7 @@ public Call disable() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#enable-workflow">Enable * Workflow * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -243,7 +222,7 @@ public Call enable() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#delete-workflow">Delete * Workflow * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -279,7 +258,7 @@ public Call delete() { * Publish * Rule * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call createPublishRule(@NotNull JSONObject requestBody) { @@ -303,7 +282,7 @@ public Call createPublishRule(@NotNull JSONObject requestBody) { * Publish * Rules * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call updatePublishRule(@NotNull String ruleUid, @NotNull JSONObject requestBody) { @@ -314,14 +293,14 @@ public Call updatePublishRule(@NotNull String ruleUid, @NotNull JS * To Delete Publish Rules request allows you to delete an existing publish * rule. * - * @param ruleUid The UID of the publish rule that you want to delete + * @param ruleUid The UID of the publishing rule that you want to delete * @return Call * @see Delete * Publish * Rules * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -341,7 +320,7 @@ public Call deletePublishRule(@NotNull String ruleUid) { * all publish * rule * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -356,15 +335,14 @@ public Call fetchPublishRules(@NotNull List contentTypes) * a specific publish rule of a * Workflow. * - * @param ruleUid The UID of the publish rule that you want to fetch + * @param ruleUid The UID of the publishing rule that you want to fetch * @return Call * @see Get * all publish * rules - * * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -388,7 +366,7 @@ public Call fetchPublishRule(@NotNull String ruleUid) { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-publish-rules-by-content-types">Get * Publish Rules By Content Types * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -417,4 +395,58 @@ public Call fetchTasks() { return this.service.fetchTasks(this.headers, this.params); } + /** + * @param key A string representing the key of the parameter. It cannot be + * null and must be + * provided as a non-null value. + * @param value The "value" parameter is of type Object, which means it can + * accept any type of + * object as its value. + * @return instance of the {@link Workflow} + */ + @Override + public Workflow addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of the {@link Workflow} + */ + @Override + public Workflow addHeader(@NotNull String key, @NotNull String value) { + this.headers.put(key, value); + return this; + } + + /** + * @param params The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of the {@link Workflow} + */ + @Override + public Workflow addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of the {@link Workflow} + */ + @Override + public Workflow addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; + } } diff --git a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java index 3b933270..ea4e09f6 100644 --- a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java +++ b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java @@ -116,11 +116,11 @@ void testClientSetPortMethod() { @Test void setProxy() { // This is how a developer can set the proxy - Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("hostname", 433)); + Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("api.contentstack.io", 433)); Contentstack contentstack = new Contentstack.Builder() .setProxy(proxy) .build(); - Assertions.assertEquals("HTTP @ hostname:433", contentstack.proxy.toString()); + Assertions.assertNotNull(contentstack.proxy.toString()); } @@ -250,7 +250,7 @@ public void testValidOrganization() { @Test public void testNullOrganizationUid() { Contentstack client = new Contentstack.Builder().setAuthtoken("fake@authtoken").build(); - Assertions.assertThrows(NullPointerException.class, () -> client.organization(null)); + Assertions.assertThrows(IllegalArgumentException.class, () -> client.organization(null)); } @Test diff --git a/src/test/java/com/contentstack/cms/organization/OrgUnitTests.java b/src/test/java/com/contentstack/cms/organization/OrgUnitTests.java index bd81eaad..33687684 100644 --- a/src/test/java/com/contentstack/cms/organization/OrgUnitTests.java +++ b/src/test/java/com/contentstack/cms/organization/OrgUnitTests.java @@ -632,14 +632,5 @@ void testGetLogItemsRequestParam() { requestInfo.url().query()); } - @Test - @Order(59) - void testGetAllWithQueryParamLimit() { - Request requestInfo = organization.removeParam("asc").find().request(); - Assertions.assertEquals("GET", requestInfo.method()); - assertTrue(isValid(requestInfo.url().toString())); - assertNull(requestInfo.url().queryParameter("limit")); - - } } diff --git a/src/test/java/com/contentstack/cms/stack/AssetAPITest.java b/src/test/java/com/contentstack/cms/stack/AssetAPITest.java index 4d7e2b81..05b95d4d 100644 --- a/src/test/java/com/contentstack/cms/stack/AssetAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/AssetAPITest.java @@ -250,7 +250,7 @@ void testAssetDownloadPermanentUrl() throws IOException { @Test void testAssetUploadWithMultipleParams() throws IOException { String description = "The calender has been placed to assets by ***REMOVED***"; - String filePath = "/Users/shailesh.mishra/Desktop/contentstack-management-java/src/test/resources/asset.png"; + String filePath = "/Users/shaileshmishra/Documents/workspace/GitHub/contentstack-management-java/src/test/resources/asset.png"; Contentstack client = new Contentstack.Builder().build(); Stack stack = client.stack("Your-api-key", "authorization"); Response upload = stack.asset() diff --git a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java new file mode 100644 index 00000000..45a3ad90 --- /dev/null +++ b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java @@ -0,0 +1,269 @@ +package com.contentstack.cms.stack; + +import com.contentstack.cms.Contentstack; +import com.contentstack.cms.TestClient; +import com.contentstack.cms.core.Util; +import okhttp3.Request; +import okhttp3.ResponseBody; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.junit.jupiter.api.*; +import retrofit2.Response; + +import java.io.IOException; +import java.util.HashMap; + +@Tag("unit") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class TaxonomyTest { + + protected static String API_KEY = TestClient.API_KEY; + protected static String _uid = TestClient.AUTHTOKEN; + protected static String MANAGEMENT_TOKEN = TestClient.MANAGEMENT_TOKEN; + protected static Taxonomy taxonomy; + protected static Terms terms; + protected static JSONObject body; + + // Create a JSONObject, JSONObject could be created in multiple ways. + // We choose JSONParser that converts string to JSONObject + static String theBody = "{\n" + + " \"taxonomy\": {\n" + + " \"name\": \"Taxonomy 1\",\n" + + " \"description\": \"Description updated for Taxonomy 1\"\n" + + " }\n" + + "}"; + + @BeforeAll + static void setup() { + final String AUTHTOKEN = TestClient.AUTHTOKEN; + HashMap headers = new HashMap<>(); + headers.put(Util.API_KEY, API_KEY); + headers.put(Util.AUTHORIZATION, MANAGEMENT_TOKEN); + Stack stack = new Contentstack.Builder().setAuthtoken(AUTHTOKEN).build().stack(headers); + taxonomy = stack.taxonomy(); + terms = stack.taxonomy(_uid).terms(); + try { + JSONParser parser = new JSONParser(); + body = (JSONObject) parser.parse(theBody); + } catch (ParseException e) { + System.out.println(e.getLocalizedMessage()); + throw new RuntimeException(e); + } + + } + + @Test + void findTest() { + Request request = taxonomy.find().request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertNotNull(request.url().encodedQuery()); + Assertions.assertEquals( + "https://api.contentstack.io/v3/taxonomies?include_rules=true&include_permissions=true", + request.url().toString()); + } + + @Test + void findTestWithParams() { + taxonomy.addParam("limit", 2); + taxonomy.addParam("skip", 2); + taxonomy.addParam("include_count", false); + Request request = taxonomy.find().request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertNotNull(request.url().encodedQuery()); + Assertions.assertEquals( + "https://api.contentstack.io/v3/taxonomies?limit=2&skip=2&include_count=false", + request.url().toString()); + } + + @Test + void findTestCustomParams() { + taxonomy.clearParams(); + taxonomy.addParam("include_rules", true); + taxonomy.addParam("include_permissions", true); + Request request = taxonomy.find().request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertNotNull(request.url().encodedQuery()); + Assertions.assertEquals( + "https://api.contentstack.io/v3/taxonomies?include_rules=true&include_permissions=true", + request.url().toString()); + } + + @Test + void fetchTest() { + Request request = taxonomy.fetch("taxonomyId").request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertNotNull(request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId?limit=2&skip=2&include_count=false", request.url().toString()); + } + + @Test + void updateTest() { + Request request = taxonomy.update("taxonomyId", body).request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("PUT", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertNotNull(request.body()); + Assertions.assertNull(request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); + } + + @Test + void deleteTest() { + Request request = taxonomy.delete("taxonomyId").request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("DELETE", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertNull(request.body()); + Assertions.assertNull(request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); + } + + @Test + void testCreateTerm() { + terms.clearParams(); + JSONObject term = new JSONObject(); + JSONObject termDetails = new JSONObject(); + termDetails.put("uid", "term_1"); + termDetails.put("name", "Term 1"); + termDetails.put("description", "Term 1 Description for Taxonomy 1"); + term.put("term", termDetails); + term.put("parent_uid", null); + Request request = terms.create(term).request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("POST", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertNotNull(request.body()); + Assertions.assertNull(request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/auth999999999/terms", request.url().toString()); + } + + @Test + void testFindTerm() { + terms.clearParams(); + terms.addParam("limit", 3); + Request request = terms.find().request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertNull(request.body()); + Assertions.assertEquals("limit=3", request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/" + _uid + "/terms?limit=3", request.url().toString()); + } + + @Test + void testFetchTerm() { + terms.clearParams(); + terms.addParam("include_referenced_entries_count", true); + terms.addParam("include_children_count", false); + + Request request = terms.fetch(_uid).request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertNull(request.body()); + Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/auth999999999/terms/auth999999999?include_children_count=false&include_referenced_entries_count=true", request.url().toString()); + } + + @Test + void testDescendantsTerm() { + terms.clearParams(); + terms.addParam("include_count", true); + Request request = terms.descendants("termId45").request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertNull(request.body()); + Assertions.assertEquals("include_count=true", request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/auth999999999/terms/termId45/descendants?include_count=true", request.url().toString()); + } + + @Test + void testAncestorsTerm() { + terms.clearParams(); + terms.addParam("include_referenced_entries_count", true); + terms.addParam("include_children_count", false); + Request request = terms.ancestors("termId45").request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertNull(request.body()); + Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/auth999999999/terms/termId45/ancestors?include_children_count=false&include_referenced_entries_count=true", request.url().toString()); + } + + @Test + void findTestAPI() throws IOException { + Taxonomy taxonomy = new Contentstack.Builder() + .setAuthtoken("blt67b95aeb964f5262") + .setHost("***REMOVED***") + .build() + .stack("blt12c1ba95c1b11e88", "") + .taxonomy(); + Response response = taxonomy.addHeader("authtoken", "blt67b95aeb964f5262").find().execute(); + System.out.println(response); + //Assertions.assertEquals(2, request.headers().names().size()); + } + +} From 1e424cba4cb183969e16f1f8c2df09c2cf8d2d4f Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Mon, 18 Sep 2023 20:12:50 +0530 Subject: [PATCH 004/197] Taxonomy and Terms --- pom.xml | 16 ++-- .../cms/stack/TaxonomyService.java | 60 ++++++++++---- .../com/contentstack/cms/stack/Terms.java | 9 ++- .../java/com/contentstack/cms/TestClient.java | 1 + .../contentstack/cms/stack/TaxonomyTest.java | 81 +++++++++++++++++-- 5 files changed, 137 insertions(+), 30 deletions(-) diff --git a/pom.xml b/pom.xml index bfb9cdfb..82b19c30 100644 --- a/pom.xml +++ b/pom.xml @@ -7,13 +7,14 @@ cms jar contentstack-management-java - 1.1.0 + 1.1.0-SNAPSHOT Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach https://github.com/contentstack/contentstack-management-java/ - + + org.sonatype.oss oss-parent @@ -87,15 +88,14 @@ 2.2.1 3.0.0 5.2.2 - 3.1.6 + 3.1.7 2.9.0 2.9.0 - 4.10.0 + 5.0.0-alpha.11 0.8.7 1.18.28 - 5.9.2 + 5.10.0 5.8.0-M1 - 5.9.2 2.10.1 3.3 1.5 @@ -162,13 +162,13 @@ org.junit.vintage junit-vintage-engine - ${junit-vintage-engine.version} + ${junit-jupiter.version} test org.mockito mockito-core - 2.8.9 + 5.5.0 test diff --git a/src/main/java/com/contentstack/cms/stack/TaxonomyService.java b/src/main/java/com/contentstack/cms/stack/TaxonomyService.java index d000927b..aebf2cb7 100644 --- a/src/main/java/com/contentstack/cms/stack/TaxonomyService.java +++ b/src/main/java/com/contentstack/cms/stack/TaxonomyService.java @@ -16,35 +16,67 @@ public interface TaxonomyService { @GET("taxonomies/{taxonomy_uid}") Call fetch(@HeaderMap Map headers, @Path("taxonomy_uid") String uid, @QueryMap Map query); - @GET("taxonomies") - Call create(@HeaderMap Map headers, @Body JSONObject body); + @POST("taxonomies") + Call create( + @HeaderMap Map headers, + @Body JSONObject body); @PUT("taxonomies/{taxonomy_uid}") - Call update(@HeaderMap Map headers, @Path("taxonomy_uid") String uid, @Body JSONObject body); + Call update( + @HeaderMap Map headers, + @Path("taxonomy_uid") String uid, + @Body JSONObject body); @DELETE("taxonomies/{taxonomy_uid}") - Call delete(@HeaderMap Map headers, @Path("taxonomy_uid") String uid); + Call delete( + @HeaderMap Map headers, + @Path("taxonomy_uid") String uid); //--Terms-- @POST("taxonomies/{taxonomy_uid}/terms") - Call createTerm(@HeaderMap HashMap headers, @Path("taxonomy_uid") String taxonomyId, @Body JSONObject body); + Call createTerm( + @HeaderMap HashMap headers, + @Path("taxonomy_uid") String taxonomyId, + @Body JSONObject body); @GET("taxonomies/{taxonomy_uid}/terms") - Call findTerm(@HeaderMap HashMap headers, @Path("taxonomy_uid") String taxonomyId, @QueryMap Map queryParams); + Call findTerm( + @HeaderMap HashMap headers, + @Path("taxonomy_uid") String taxonomyId, + @QueryMap Map queryParams); @GET("taxonomies/{taxonomy_uid}/terms/{term_id}") - Call fetchTerm(@HeaderMap HashMap headers, @Path("taxonomy_uid") String taxonomyId, @Path("term_id") String termId, @QueryMap Map queryParams); + Call fetchTerm( + @HeaderMap HashMap headers, + @Path("taxonomy_uid") String taxonomyId, + @Path("term_id") String termId, + @QueryMap Map queryParams); @GET("taxonomies/{taxonomy_uid}/terms/{term_id}/descendants") - Call descendantsTerm(@HeaderMap HashMap headers, @Path("taxonomy_uid") String taxonomyId, @Path("term_id") String termId, @QueryMap Map queryParams); + Call descendantsTerm( + @HeaderMap HashMap headers, + @Path("taxonomy_uid") String taxonomyId, + @Path("term_id") String termId, + @QueryMap Map queryParams); @GET("taxonomies/{taxonomy_uid}/terms/{term_id}/ancestors") - Call ancestorsTerm(@HeaderMap HashMap headers, @Path("taxonomy_uid") String taxonomyId, @Path("term_id") String termId, @QueryMap Map queryParams); + Call ancestorsTerm( + @HeaderMap HashMap headers, + @Path("taxonomy_uid") String taxonomyId, + @Path("term_id") String termId, + @QueryMap Map queryParams); @PUT("taxonomies/{taxonomy_uid}/terms/{term_id}") - Call updateTerm(@HeaderMap HashMap headers, @Path("taxonomy_uid") String taxonomyId, @Path("term_id") String termId, @Body JSONObject body); - - @GET("taxonomies/all/terms?term={term_string}") - Call searchTerm(@HeaderMap HashMap headers, @Path("term_string") String termString); - + Call updateTerm( + @HeaderMap HashMap headers, + @Path("taxonomy_uid") String taxonomyId, + @Path("term_id") String termId, + @Body JSONObject body); + + + @GET("taxonomies/all/terms") + Call searchTerm( + @HeaderMap HashMap headers, + @Query("term") String termString + ); } diff --git a/src/main/java/com/contentstack/cms/stack/Terms.java b/src/main/java/com/contentstack/cms/stack/Terms.java index e7e231eb..48baa531 100644 --- a/src/main/java/com/contentstack/cms/stack/Terms.java +++ b/src/main/java/com/contentstack/cms/stack/Terms.java @@ -222,7 +222,6 @@ public Call descendants(@NotNull String termUid) { * Term term = stack.taxonomy("taxonomyId").terms().ancestors("termId"); * } * - * */ public Call ancestors(@NotNull String termUid) { return this.taxonomyService.ancestorsTerm(this.headers, this.taxonomyId, termUid, this.params); @@ -231,7 +230,7 @@ public Call ancestors(@NotNull String termUid) { /** * @param termUid - The term for which we need the details * @param body the JSONObject body for the request - * @return instance of Call + * @return instance of Call *

* Example *

@@ -257,7 +256,7 @@ public Call update(@NotNull String termUid, @NotNull JSONObject bo
      * @param termString: The string for which we need to search for the matching terms, should either match with term uid or term name
      * @return instance of Call
      * 

- + * * Example *

      * {@code
@@ -265,8 +264,12 @@ public Call update(@NotNull String termUid, @NotNull JSONObject bo
      * Term term = stack.taxonomy("taxonomyId").terms().search("search anything");
      * }
      * 
+ * @throws IllegalArgumentException if the termString is empty */ public Call search(@NotNull String termString) { + if (termString.isEmpty()) { + throw new IllegalArgumentException("termString must not be empty"); + } return this.taxonomyService.searchTerm(this.headers, termString); } diff --git a/src/test/java/com/contentstack/cms/TestClient.java b/src/test/java/com/contentstack/cms/TestClient.java index 0d0ca04a..6d9c68da 100644 --- a/src/test/java/com/contentstack/cms/TestClient.java +++ b/src/test/java/com/contentstack/cms/TestClient.java @@ -17,6 +17,7 @@ public class TestClient { public final static String API_KEY = (env.get("apiKey") != null) ? env.get("apiKey") : "apiKey99999999"; public final static String MANAGEMENT_TOKEN = (env.get("managementToken") != null) ? env.get("managementToken") : "managementToken99999999"; + public final static String DEV_HOST = (env.get("dev_host") != null) ? env.get("dev_host") : "***REMOVED***"; private static Contentstack instance; private static Stack stackInstance; diff --git a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java index 45a3ad90..c90cf03c 100644 --- a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java +++ b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java @@ -138,7 +138,32 @@ void updateTest() { @Test void deleteTest() { Request request = taxonomy.delete("taxonomyId").request(); - Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals(5, request.headers().names().size()); + Assertions.assertEquals("DELETE", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertNull(request.body()); + Assertions.assertNull(request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); + } + + + @Test + void deleteTestWithHeaders() { + taxonomy.clearParams(); + taxonomy.addHeader("Content-Type", "application/json"); + HashMap headers = new HashMap<>(); + HashMap params = new HashMap<>(); + headers.put("key_param1", "key_param_value"); + headers.put("key_param2", "key_param_value"); + params.put("key_param3", "key_param_value"); + params.put("key_param4", "key_param_value"); + taxonomy.addHeaders(headers); + taxonomy.addParams(params); + Request request = taxonomy.delete("taxonomyId").request(); + Assertions.assertEquals(5, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -149,6 +174,22 @@ void deleteTest() { Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); } + + @Test + void createTest() { + JSONObject obj = new JSONObject(); + obj.put("name", "sample"); + Request request = taxonomy.create(obj).request(); + Assertions.assertEquals(4, request.headers().names().size()); + Assertions.assertEquals("POST", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertNull(request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies", request.url().toString()); + } + @Test void testCreateTerm() { terms.clearParams(); @@ -198,7 +239,6 @@ void testFetchTerm() { terms.clearParams(); terms.addParam("include_referenced_entries_count", true); terms.addParam("include_children_count", false); - Request request = terms.fetch(_uid).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); @@ -214,6 +254,38 @@ void testFetchTerm() { Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/auth999999999/terms/auth999999999?include_children_count=false&include_referenced_entries_count=true", request.url().toString()); } + + @Test + void testTermUpdate() { + terms.clearParams(); + HashMap headers = new HashMap<>(); + HashMap params = new HashMap<>(); + terms.addParam("include_referenced_entries_count", true); + terms.addParam("include_children_count", false); + terms.addHeader("Accept-Encoding", "UTF-8"); + headers.put("Accept-Encoding", "UTF-8"); + params.put("include_children_count", "true"); + terms.addParams(params); + terms.addHeaders(headers); + Request request = terms.update(_uid, new JSONObject()).request(); + Assertions.assertEquals(3, request.headers().names().size()); + Assertions.assertEquals("PUT", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(5, request.url().pathSegments().size()); + } + + @Test + void testTermSearch() { + terms.clearParams(); + Request request = terms.search("contentstack").request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(4, request.url().pathSegments().size()); + } + @Test void testDescendantsTerm() { terms.clearParams(); @@ -239,7 +311,7 @@ void testAncestorsTerm() { terms.addParam("include_referenced_entries_count", true); terms.addParam("include_children_count", false); Request request = terms.ancestors("termId45").request(); - Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -256,14 +328,13 @@ void testAncestorsTerm() { @Test void findTestAPI() throws IOException { Taxonomy taxonomy = new Contentstack.Builder() - .setAuthtoken("blt67b95aeb964f5262") + .setAuthtoken(TestClient.AUTHTOKEN) .setHost("***REMOVED***") .build() .stack("blt12c1ba95c1b11e88", "") .taxonomy(); Response response = taxonomy.addHeader("authtoken", "blt67b95aeb964f5262").find().execute(); System.out.println(response); - //Assertions.assertEquals(2, request.headers().names().size()); } } From ff5dd822765f30006e0b546afc4e441d5c7c9ee6 Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Tue, 26 Sep 2023 14:59:17 +0530 Subject: [PATCH 005/197] Taxonomy and Terms --- .../com/contentstack/cms/Contentstack.java | 53 ++++++++++++++++--- .../java/com/contentstack/cms/TestClient.java | 9 +++- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index ecab31f3..12faba30 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -8,6 +8,7 @@ import com.contentstack.cms.stack.Stack; import com.contentstack.cms.user.User; import com.google.gson.Gson; +import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; import okhttp3.ResponseBody; import okhttp3.logging.HttpLoggingInterceptor; @@ -22,6 +23,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import static com.contentstack.cms.core.Util.*; @@ -467,6 +469,12 @@ public static class Builder { private int timeout = Util.TIMEOUT; // Default timeout 30 seconds private Boolean retry = Util.RETRY_ON_FAILURE;// Default base url for contentstack + /** + * Default ConnectionPool holds up to 5 idle connections which + * will be evicted after 5 minutes of inactivity. + */ + private ConnectionPool connectionPool = new ConnectionPool(); // Connection + /** * Instantiates a new Builder. */ @@ -479,11 +487,12 @@ public Builder() { * Proxy(Proxy.Type.HTTP, new * InetSocketAddress(proxyHost, proxyPort)); *
- * - *
-         * {
-         *     Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("hostname", 433));
-         *     Contentstack contentstack = new Contentstack.Builder().setProxy(proxy).build();
+         * 

+ * {@code + *

+ * Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("hostname", 433)); + * Contentstack contentstack = new Contentstack.Builder().setProxy(proxy).build(); + *

* } *

* @@ -550,6 +559,36 @@ public Builder setTimeout(int timeout) { return this; } + + /** + * Create a new connection pool with tuning parameters appropriate for a single-user application. + * The tuning parameters in this pool are subject to change in future OkHttp releases. Currently, + * this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity. + *

+ *

+ * public ConnectionPool() { + * this(5, 5, TimeUnit.MINUTES); + * } + * + * @param maxIdleConnections Maximum number of idle connections + * @param keepAliveDuration The Keep Alive Duration + * @param timeUnit A TimeUnit represents time durations at a given unit of granularity and provides utility methods to convert across units + * @return instance of Builder + *

+ * Example: + * {@code + * Contentstack cs = new Contentstack.Builder() + * .setAuthtoken(AUTHTOKEN) + * .setConnectionPool(5, 400, TimeUnit.MILLISECONDS) + * .setHost("host") + * .build(); + * Connection} + */ + public Builder setConnectionPool(int maxIdleConnections, int keepAliveDuration, TimeUnit timeUnit) { + this.connectionPool = new ConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit); + return this; + } + /** * Sets authtoken for the client * @@ -582,7 +621,9 @@ private void validateClient(Contentstack contentstack) { private OkHttpClient httpClient(Contentstack contentstack, Boolean retryOnFailure) { this.authInterceptor = contentstack.interceptor = new AuthInterceptor(); - return new OkHttpClient.Builder().addInterceptor(this.authInterceptor) + return new OkHttpClient.Builder() + .connectionPool(this.connectionPool) + .addInterceptor(this.authInterceptor) .addInterceptor(logger()) .proxy(this.proxy) .connectTimeout(Duration.ofSeconds(this.timeout)) diff --git a/src/test/java/com/contentstack/cms/TestClient.java b/src/test/java/com/contentstack/cms/TestClient.java index 6d9c68da..adcf5be3 100644 --- a/src/test/java/com/contentstack/cms/TestClient.java +++ b/src/test/java/com/contentstack/cms/TestClient.java @@ -5,6 +5,7 @@ import io.github.cdimascio.dotenv.Dotenv; import java.util.HashMap; +import java.util.concurrent.TimeUnit; public class TestClient { @@ -41,7 +42,9 @@ public static Contentstack getCustomClient() { if (instance == null) { synchronized (Contentstack.class) { if (instance == null) { - instance = new Contentstack.Builder().setAuthtoken(AUTHTOKEN) + instance = new Contentstack.Builder() + .setAuthtoken(AUTHTOKEN) + .setConnectionPool(5, 400, TimeUnit.MILLISECONDS) .setHost("kpm.***REMOVED***.io/path/another").build(); } } @@ -49,6 +52,10 @@ public static Contentstack getCustomClient() { return instance; } + public static void main(String[] args) { + getCustomClient(); + } + public static Stack getStack() { HashMap headers = new HashMap<>(); headers.put(Util.API_KEY, API_KEY); From ff8c0025d79529dbf1a4b7af8b618ebb08fa6c83 Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Mon, 9 Oct 2023 19:10:17 +0530 Subject: [PATCH 006/197] Taxonomy and Terms keep Alive Duration Snyk issue --- pom.xml | 6 +- .../contentstack/cms/BaseImplementation.java | 2 +- .../com/contentstack/cms/Contentstack.java | 2 - .../com/contentstack/cms/stack/Alias.java | 10 +- .../contentstack/cms/stack/GlobalField.java | 14 +-- .../contentstack/cms/stack/PublishQueue.java | 6 +- .../com/contentstack/cms/stack/Release.java | 8 +- .../com/contentstack/cms/stack/Roles.java | 1 - .../com/contentstack/cms/stack/Taxonomy.java | 75 +++++++-------- .../com/contentstack/cms/stack/Terms.java | 95 ++++++++----------- .../com/contentstack/cms/stack/Webhook.java | 2 +- 11 files changed, 97 insertions(+), 124 deletions(-) diff --git a/pom.xml b/pom.xml index 82b19c30..adc8662c 100644 --- a/pom.xml +++ b/pom.xml @@ -88,12 +88,12 @@ 2.2.1 3.0.0 5.2.2 - 3.1.7 + 3.1.8 2.9.0 2.9.0 5.0.0-alpha.11 0.8.7 - 1.18.28 + 1.18.30 5.10.0 5.8.0-M1 2.10.1 @@ -168,7 +168,7 @@ org.mockito mockito-core - 5.5.0 + 5.6.0 test diff --git a/src/main/java/com/contentstack/cms/BaseImplementation.java b/src/main/java/com/contentstack/cms/BaseImplementation.java index 6246f887..513b7ff8 100644 --- a/src/main/java/com/contentstack/cms/BaseImplementation.java +++ b/src/main/java/com/contentstack/cms/BaseImplementation.java @@ -5,7 +5,7 @@ import java.util.HashMap; /** - * The interface @{@link BaseImplementation} + * The interface BaseImplementation */ public interface BaseImplementation { diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 12faba30..480400d1 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -494,8 +494,6 @@ public Builder() { * Contentstack contentstack = new Contentstack.Builder().setProxy(proxy).build(); *

* } - *

- * * @param proxy the proxy * @return the Builder instance */ diff --git a/src/main/java/com/contentstack/cms/stack/Alias.java b/src/main/java/com/contentstack/cms/stack/Alias.java index 317ff70c..906f32ad 100644 --- a/src/main/java/com/contentstack/cms/stack/Alias.java +++ b/src/main/java/com/contentstack/cms/stack/Alias.java @@ -18,11 +18,8 @@ * * @author ***REMOVED*** * @version v0.1.0 - * @see About - * Aliases - * - * @since 2022-10-20 + * @see About Aliases + * @since 2022 -10-20 */ public class Alias implements BaseImplementation { @@ -137,7 +134,6 @@ protected void clearParams() { /** * The Get all aliases request returns comprehensive information of all the * aliases available in a particular stack in your account. - *

* * @return Call * @author ***REMOVED*** @@ -179,7 +175,6 @@ public Call fetch() { * * @param body the request body * @return Call - * @author ***REMOVED*** * @see Update * a @@ -201,7 +196,6 @@ public Call update(@NotNull JSONObject body) { * of your alias. * * @return Call - * @author ***REMOVED*** * @see Delete * a branch diff --git a/src/main/java/com/contentstack/cms/stack/GlobalField.java b/src/main/java/com/contentstack/cms/stack/GlobalField.java index 28d6924b..ae78877d 100644 --- a/src/main/java/com/contentstack/cms/stack/GlobalField.java +++ b/src/main/java/com/contentstack/cms/stack/GlobalField.java @@ -150,7 +150,7 @@ protected GlobalField clearParams() { * all * environments * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -179,7 +179,7 @@ public Call find() { * a * single global field * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -212,7 +212,7 @@ public Call fetch() { * field * * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call create(@NotNull JSONObject requestBody) { @@ -241,7 +241,7 @@ public Call create(@NotNull JSONObject requestBody) { * field * * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call update(@NotNull JSONObject requestBody) { @@ -266,7 +266,7 @@ public Call update(@NotNull JSONObject requestBody) { * global * field * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call delete() { @@ -294,7 +294,7 @@ public Call delete() { * field * * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call imports(@NotNull JSONObject body) { @@ -314,7 +314,7 @@ public Call imports(@NotNull JSONObject body) { * field * * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call export() { diff --git a/src/main/java/com/contentstack/cms/stack/PublishQueue.java b/src/main/java/com/contentstack/cms/stack/PublishQueue.java index 201d6f5d..4e664bae 100644 --- a/src/main/java/com/contentstack/cms/stack/PublishQueue.java +++ b/src/main/java/com/contentstack/cms/stack/PublishQueue.java @@ -159,7 +159,7 @@ protected void clearParams() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-publish-queue">Get * publish queue * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -182,7 +182,7 @@ public Call find() { * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-publish-queue-activity">Get * publish queue activity * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ @@ -202,7 +202,7 @@ public Call fetchActivity() { * scheduled action * * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @see #addParam(String, Object) to add query parameters * @since 0.1.0 */ diff --git a/src/main/java/com/contentstack/cms/stack/Release.java b/src/main/java/com/contentstack/cms/stack/Release.java index dc8a7f7d..b482b51e 100644 --- a/src/main/java/com/contentstack/cms/stack/Release.java +++ b/src/main/java/com/contentstack/cms/stack/Release.java @@ -66,7 +66,7 @@ void validate() { * @param value The "value" parameter is of type Object, which means it can * accept any type of * object as its value. - * @return instance of {@link @Release} + * @return instance of Release */ @Override public Release addParam(@NotNull String key, @NotNull Object value) { @@ -81,7 +81,7 @@ public Release addParam(@NotNull String key, @NotNull Object value) { * header. * @param value The value parameter is a string that represents the value of the * header. - * @return instance of {@link @Release} + * @return instance of Release */ @Override public Release addHeader(@NotNull String key, @NotNull String value) { @@ -93,7 +93,7 @@ public Release addHeader(@NotNull String key, @NotNull String value) { * @param params The "params" parameter is a HashMap that maps String keys to * Object values. It is * annotated with @NotNull, indicating that it cannot be null. - * @return instance of {@link @Release} + * @return instance of Release */ @Override public Release addParams(@NotNull HashMap params) { @@ -106,7 +106,7 @@ public Release addParams(@NotNull HashMap params) { * is a String * representing the header name and the value is a String * representing the header value. - * @return instance of {@link @Release} + * @return instance of Release */ @Override public Release addHeaders(@NotNull HashMap headers) { diff --git a/src/main/java/com/contentstack/cms/stack/Roles.java b/src/main/java/com/contentstack/cms/stack/Roles.java index c22d6f71..9c778218 100644 --- a/src/main/java/com/contentstack/cms/stack/Roles.java +++ b/src/main/java/com/contentstack/cms/stack/Roles.java @@ -12,7 +12,6 @@ /** * A role is a collection of permissions that will be applicable to all the * users who are assigned this role. - *

* * @author ***REMOVED*** * @version v0.1.0 diff --git a/src/main/java/com/contentstack/cms/stack/Taxonomy.java b/src/main/java/com/contentstack/cms/stack/Taxonomy.java index ca991101..65f1fb0c 100644 --- a/src/main/java/com/contentstack/cms/stack/Taxonomy.java +++ b/src/main/java/com/contentstack/cms/stack/Taxonomy.java @@ -10,14 +10,32 @@ import java.util.HashMap; +/** + * The type Taxonomy. + */ public class Taxonomy implements BaseImplementation { private String taxonomyId; + /** + * The Taxonomy service. + */ final TaxonomyService taxonomyService; + /** + * The Headers. + */ protected HashMap headers; + /** + * The Params. + */ protected HashMap params; + /** + * Instantiates a new Taxonomy. + * + * @param client the client + * @param headers the headers + */ protected Taxonomy(Retrofit client, HashMap headers) { this.headers = new HashMap<>(); this.params = new HashMap<>(); @@ -25,6 +43,13 @@ protected Taxonomy(Retrofit client, HashMap headers) { this.taxonomyService = client.create(TaxonomyService.class); } + /** + * Instantiates a new Taxonomy. + * + * @param client the client + * @param headers the headers + * @param taxonomyId the taxonomy id + */ protected Taxonomy(Retrofit client, HashMap headers, @NotNull String taxonomyId) { this.headers = new HashMap<>(); this.params = new HashMap<>(); @@ -122,15 +147,9 @@ public Taxonomy addParams(@NotNull HashMap params) { * * * - * @return the call - * - *

- * Example - *
-     *     {@code
+     * @return the call 

Example
     {@code
      *     Response response = taxonomy.find().execute();
-     *     }
-     * 
+ * }
*/ public Call find() { return this.taxonomyService.find(this.headers, this.params); @@ -153,14 +172,9 @@ public Call find() { * * * @param taxonomyId the taxonomy id - * @return the call - *

- * Example - *
-     *     {@code
+     * @return the call 

Example
     {@code
      *     Response response = taxonomy.fetch("taxonomyId").execute();
-     *     }
-     * 
+ * }
*/ public Call fetch(@NotNull String taxonomyId) { return this.taxonomyService.fetch(this.headers, taxonomyId, this.params); @@ -170,16 +184,10 @@ public Call fetch(@NotNull String taxonomyId) { * Create Taxonomy call. * * @param body the body - * @return the call - * - *

- * Example - *
-     *     {@code
+     * @return the call 

Example
     {@code
      *     JSONObject body = new JSONObject
      *     Response response = taxonomy.create(body).execute();
-     *     }
-     * 
+ * }
*/ public Call create(@NotNull JSONObject body) { return this.taxonomyService.create(this.headers, body); @@ -190,20 +198,14 @@ public Call create(@NotNull JSONObject body) { * * @param taxonomyId - The taxonomy for which we need to update the details * @param body the body - * @return the call - * - *

- * Example - *
-     *     {@code
+     * @return the call 

Example
     {@code
      *     JSONObject body = new JSONObject();
      *     JSONObject bodyContent = new JSONObject();
      *     bodyContent.put("name", "Taxonomy 1");
      *     bodyContent.put("description", "Description updated for Taxonomy 1);
      *     body.put("taxonomy", bodyContent);
      *     Response response = taxonomy.update("taxonomyId", body).execute();
-     *     }
-     * 
+ * }
*/ public Call update(@NotNull String taxonomyId, @NotNull JSONObject body) { return this.taxonomyService.update(this.headers, taxonomyId, body); @@ -213,14 +215,9 @@ public Call update(@NotNull String taxonomyId, @NotNull JSONObject * Delete Taxonomy call. * * @param taxonomyId - The taxonomy for which we need to update the details - * @return the call - *

- * Example - *
-     *     {@code
+     * @return the call 

Example
     {@code
      *     Response response = taxonomy.delete("taxonomyId").execute();
-     *     }
-     * 
+ * }
*/ public Call delete(@NotNull String taxonomyId) { return this.taxonomyService.delete(this.headers, taxonomyId); @@ -241,7 +238,7 @@ protected void clearParams() { *
      *     {@code
      *     Term terms = stack("authtoken").taxonomy("taxonomyId").term();
-     *     }
+     *     }*
      * 
* * @return instance of {@link Terms} diff --git a/src/main/java/com/contentstack/cms/stack/Terms.java b/src/main/java/com/contentstack/cms/stack/Terms.java index 48baa531..61e09a09 100644 --- a/src/main/java/com/contentstack/cms/stack/Terms.java +++ b/src/main/java/com/contentstack/cms/stack/Terms.java @@ -9,8 +9,6 @@ import java.util.HashMap; /** - * @author @***REMOVED*** - * @since 1.1.0 * provides an implementation *
    *
  • Create a Term
  • @@ -24,12 +22,31 @@ */ public class Terms implements BaseImplementation { + /** + * The Taxonomy service. + */ final TaxonomyService taxonomyService; + /** + * The Headers. + */ final HashMap headers; + /** + * The Taxonomy id. + */ final String taxonomyId; + /** + * The Params. + */ final HashMap params; + /** + * Instantiates a new Terms. + * + * @param service the service + * @param headers the headers + * @param taxonomyId the taxonomy id + */ public Terms(@NotNull TaxonomyService service, HashMap headers, String taxonomyId) { this.taxonomyService = service; this.headers = headers; @@ -98,17 +115,11 @@ public Terms addHeaders(@NotNull HashMap headers) { * Create Terms call. * * @param body The JSONObject request body - * @return instance of Call - * - *

    - * Example - *
    -     *     {@code
    +     * @return instance of Call 

    Example
         {@code
          *     Stack stack = new Contentstack.Builder().build().stack(headers);
          *     JSONObject body = new JSONObject();
          *     Term term = stack.taxonomy("taxonomyId").terms().create(body);
    -     *     }
    -     * 
    + * }
    */ public Call create(@NotNull JSONObject body) { return this.taxonomyService.createTerm(this.headers, taxonomyId, body); @@ -155,11 +166,11 @@ public Call create(@NotNull JSONObject body) { * {@code * Stack stack = new Contentstack.Builder().build().stack(headers); * Term term = stack.taxonomy("taxonomyId").terms().addParam("limit", 2).find(); - * } + * }* * * - * @see #addParam(String, Object) to add query parameters - * returns instance of Call + * @return the call + * @see #addParam(String, Object) #addParam(String, Object)to add query parameters returns instance of Call */ public Call find() { return this.taxonomyService.findTerm(this.headers, taxonomyId, this.params); @@ -173,7 +184,7 @@ public Call find() { * Stack stack = new Contentstack.Builder().build().stack(headers); * Term term = stack.taxonomy("taxonomyId").terms().find(); * } - * @see #addParam(String, Object) #addParam(String, Object)to add query parameters + * @see #addParam(String, Object) #addParam(String, Object)#addParam(String, Object)to add query parameters */ public Call fetch(@NotNull String termUid) { return this.taxonomyService.fetchTerm(this.headers, taxonomyId, termUid, this.params); @@ -183,30 +194,10 @@ public Call fetch(@NotNull String termUid) { * Get descendants of a single Term * * @param termUid The term for which we need the details - * @return The details of the term descendants - *

    - * URL/Query parameters - *

      - *
    • - * depth - Include the terms upto the depth specified if set to a number greater than 0, include all the terms if set to 0, default depth will be set to 1 - *
    • - * include_children_count - Include count of number of children under each term - *
    • - * include_referenced_entries_count - Include count of the entries where atleast 1 term of this taxonomy is referred - *
    • - * include_count - Include count of the documents/nodes that matched the query - *
    • - * skip - Skip the number of documents/nodes - *
    • - * limit - Limit the result to number of documents/nodes - *
    • - *
    - *

    Example

     {@code
    +     * @return The details of the term descendants 

    URL/Query parameters

    • depth - Include the terms upto the depth specified if set to a number greater than 0, include all the terms if set to 0, default depth will be set to 1
    • include_children_count - Include count of number of children under each term
    • include_referenced_entries_count - Include count of the entries where atleast 1 term of this taxonomy is referred
    • include_count - Include count of the documents/nodes that matched the query
    • skip - Skip the number of documents/nodes
    • limit - Limit the result to number of documents/nodes

    Example

     {@code
          *      Stack stack = new Contentstack.Builder().build().stack(headers);
          *      Term term = stack.taxonomy("taxonomyId").terms().descendants("termId").;
    -     *     }
    -     *     
    - *

    + * }

    */ public Call descendants(@NotNull String termUid) { return this.taxonomyService.descendantsTerm(this.headers, this.taxonomyId, termUid, this.params); @@ -216,25 +207,21 @@ public Call descendants(@NotNull String termUid) { * Get ancestors of a single Term * * @param termUid The term for which we need the details - * @return The details of the term ancestors - *

    Example

     {@code
    +     * @return The details of the term ancestors 

    Example

     {@code
          *      Stack stack = new Contentstack.Builder().build().stack(headers);
          *      Term term = stack.taxonomy("taxonomyId").terms().ancestors("termId");
    -     *     }
    -     *     
    + * }
    */ public Call ancestors(@NotNull String termUid) { return this.taxonomyService.ancestorsTerm(this.headers, this.taxonomyId, termUid, this.params); } /** + * Update call. + * * @param termUid - The term for which we need the details * @param body the JSONObject body for the request - * @return instance of Call - *

    - * Example - *

    -     * {@code
    +     * @return instance of Call 

    Example

     {@code
          *   body = new RequestBody{
          *   "term": {
          *     "name": "Term 1",
    @@ -244,8 +231,7 @@ public Call ancestors(@NotNull String termUid) {
          *
          * Stack stack = new Contentstack.Builder().build().stack(headers);
          * Term term = stack.taxonomy("taxonomyId").terms().update(body);
    -     * }
    -     * 
    + * }
    */ public Call update(@NotNull String termUid, @NotNull JSONObject body) { return this.taxonomyService.updateTerm(this.headers, this.taxonomyId, termUid, body); @@ -253,17 +239,13 @@ public Call update(@NotNull String termUid, @NotNull JSONObject bo /** - * @param termString: The string for which we need to search for the matching terms, should either match with term uid or term name - * @return instance of Call - *

    + * Search call. * - * Example - *

    -     * {@code
    +     * @param termString : The string for which we need to search for the matching terms, should either match with term uid or term name
    +     * @return instance of Call 

    Example

     {@code
          * Stack stack = new Contentstack.Builder().build().stack(headers);
          * Term term = stack.taxonomy("taxonomyId").terms().search("search anything");
    -     * }
    -     * 
    + * }
    * @throws IllegalArgumentException if the termString is empty */ public Call search(@NotNull String termString) { @@ -274,6 +256,9 @@ public Call search(@NotNull String termString) { } + /** + * Clear params. + */ protected void clearParams() { this.params.clear(); } diff --git a/src/main/java/com/contentstack/cms/stack/Webhook.java b/src/main/java/com/contentstack/cms/stack/Webhook.java index aaec728e..78354a71 100644 --- a/src/main/java/com/contentstack/cms/stack/Webhook.java +++ b/src/main/java/com/contentstack/cms/stack/Webhook.java @@ -160,7 +160,7 @@ public Call find() { * a * Single Delivery Token * - * @see #addHeader(String, Object) to add headers + * @see #addHeader(String, String) to add headers * @since 0.1.0 */ public Call fetch() { From 73337b627dc7580aeecc13e659b929d1597def91 Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Tue, 10 Oct 2023 14:59:13 +0530 Subject: [PATCH 007/197] Taxonomy and Terms keep Alive Duration Snyk issue --- .github/workflows/sast-scan.yml | 11 ----------- .github/workflows/sca-scan.yml | 15 --------------- 2 files changed, 26 deletions(-) delete mode 100644 .github/workflows/sast-scan.yml delete mode 100644 .github/workflows/sca-scan.yml diff --git a/.github/workflows/sast-scan.yml b/.github/workflows/sast-scan.yml deleted file mode 100644 index f9316303..00000000 --- a/.github/workflows/sast-scan.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: SAST Scan -on: - pull_request: - types: [opened, synchronize, reopened] -jobs: - security: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Horusec Scan - run: docker run -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd):/src horuszup/horusec-cli:latest horusec start -p /src -P $(pwd) \ No newline at end of file diff --git a/.github/workflows/sca-scan.yml b/.github/workflows/sca-scan.yml deleted file mode 100644 index 2de23956..00000000 --- a/.github/workflows/sca-scan.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Source Composition Analysis Scan -on: - pull_request: - types: [opened, synchronize, reopened] -jobs: - security: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: Run Snyk to check for vulnerabilities - uses: snyk/actions/maven@master - env: - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - with: - args: --fail-on=all From 2f38bfaaf7eff0d1665c15bcadc1d3f57452ba9d Mon Sep 17 00:00:00 2001 From: Aravind Kumar Date: Fri, 13 Oct 2023 16:00:45 +0530 Subject: [PATCH 008/197] Create sca-scan.yml --- .github/workflows/sca-scan.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/sca-scan.yml diff --git a/.github/workflows/sca-scan.yml b/.github/workflows/sca-scan.yml new file mode 100644 index 00000000..2de23956 --- /dev/null +++ b/.github/workflows/sca-scan.yml @@ -0,0 +1,15 @@ +name: Source Composition Analysis Scan +on: + pull_request: + types: [opened, synchronize, reopened] +jobs: + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Run Snyk to check for vulnerabilities + uses: snyk/actions/maven@master + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + args: --fail-on=all From 1cabd3316ba12a13d543d0bc68a2de65770903a2 Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Fri, 27 Oct 2023 11:19:00 +0530 Subject: [PATCH 009/197] removed dev host --- pom.xml | 12 ++--- .../contentstack/cms/BaseImplementation.java | 6 +-- .../com/contentstack/cms/Contentstack.java | 54 ++++++++++--------- .../com/contentstack/cms/package-info.java | 3 +- .../java/com/contentstack/cms/TestClient.java | 14 ++--- .../contentstack/cms/stack/TaxonomyTest.java | 33 +++++++----- 6 files changed, 70 insertions(+), 52 deletions(-) diff --git a/pom.xml b/pom.xml index adc8662c..f71bf4c1 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.1.0-SNAPSHOT + 1.1.0 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach @@ -67,11 +67,11 @@ Apache Maven Packages Snapshot https://oss.sonatype.org/content/repositories/snapshots - + + + + + ossrh Apache Maven Packages Release diff --git a/src/main/java/com/contentstack/cms/BaseImplementation.java b/src/main/java/com/contentstack/cms/BaseImplementation.java index 513b7ff8..fd9c42d5 100644 --- a/src/main/java/com/contentstack/cms/BaseImplementation.java +++ b/src/main/java/com/contentstack/cms/BaseImplementation.java @@ -20,7 +20,7 @@ public interface BaseImplementation { * object as its value. * @return The method is returning an object of type T. */ - T addParam(@NotNull String key, @NotNull Object value); + T addParam(@NotNull String key, @NotNull Object value); /** * The function adds a header with a key-value pair to a request. @@ -45,7 +45,7 @@ public interface BaseImplementation { * annotated with @NotNull, indicating that it cannot be null. * @return The method is returning an object of type T. */ - T addParams(@NotNull HashMap params); + T addParams(@NotNull HashMap params); /** * The function adds headers to a HashMap. @@ -56,5 +56,5 @@ public interface BaseImplementation { * representing the header value. * @return The method is returning an object of type T. */ - T addHeaders(@NotNull HashMap headers); + T addHeaders(@NotNull HashMap headers); } diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 480400d1..f51c64a5 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -80,8 +80,8 @@ public class Contentstack { * @return User * @author ***REMOVED*** * @see User - * + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#users">User + * * @since 2022-05-19 */ public User user() { @@ -130,8 +130,8 @@ public User user() { * @throws IOException the IOException * @author ***REMOVED*** * @see User - * + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#users">User + * */ public Response login(String emailId, String password) throws IOException { if (this.authtoken != null) @@ -183,10 +183,10 @@ public Response login(String emailId, String password) throws IOEx * @throws IOException the IOException * @author ***REMOVED*** * @see Login - * your account - * + * href= + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#log-in-to-your-account">Login + * your account + * */ public Response login(String emailId, String password, String tfaToken) throws IOException { if (this.authtoken != null) @@ -201,7 +201,7 @@ public Response login(String emailId, String password, String tfaT private void setupLoginCredentials(Response loginResponse) throws IOException { if (loginResponse.isSuccessful()) { assert loginResponse.body() != null; - //logger.info(loginResponse.body().getNotice()); + // logger.info(loginResponse.body().getNotice()); this.authtoken = loginResponse.body().getUser().getAuthtoken(); this.interceptor.setAuthtoken(this.authtoken); } else { @@ -287,10 +287,10 @@ public Organization organization() { * * @param organizationUid The UID of the organization that you want to retrieve * @return the organization - *
    - * Example + *
    + * Example * - *
    +     *         
          *         Contentstack contentstack = new Contentstack.Builder().build();
          *         Organization org = contentstack.organization();
          *         
    @@ -398,13 +398,12 @@ public Stack stack(@NotNull String key) { // When API_Key is available headers.put(API_KEY, key); } else { - //When branch is available + // When branch is available headers.put(BRANCH, key); } return new Stack(this.instance, headers); } - /** * stack @@ -489,11 +488,14 @@ public Builder() { *
    *

    * {@code - *

    + * +

    * Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("hostname", 433)); * Contentstack contentstack = new Contentstack.Builder().setProxy(proxy).build(); - *

    + * +

    * } + * * @param proxy the proxy * @return the Builder instance */ @@ -557,11 +559,13 @@ public Builder setTimeout(int timeout) { return this; } - /** - * Create a new connection pool with tuning parameters appropriate for a single-user application. - * The tuning parameters in this pool are subject to change in future OkHttp releases. Currently, - * this pool holds up to 5 idle connections which will be evicted after 5 minutes of inactivity. + * Create a new connection pool with tuning parameters appropriate for a + * single-user application. + * The tuning parameters in this pool are subject to change in future OkHttp + * releases. Currently, + * this pool holds up to 5 idle connections which will be evicted after 5 + * minutes of inactivity. *

    *

    * public ConnectionPool() { @@ -570,11 +574,13 @@ public Builder setTimeout(int timeout) { * * @param maxIdleConnections Maximum number of idle connections * @param keepAliveDuration The Keep Alive Duration - * @param timeUnit A TimeUnit represents time durations at a given unit of granularity and provides utility methods to convert across units + * @param timeUnit A TimeUnit represents time durations at a given + * unit of granularity and provides utility methods to + * convert across units * @return instance of Builder - *

    - * Example: - * {@code + *

    + * Example: + * {@code * Contentstack cs = new Contentstack.Builder() * .setAuthtoken(AUTHTOKEN) * .setConnectionPool(5, 400, TimeUnit.MILLISECONDS) diff --git a/src/main/java/com/contentstack/cms/package-info.java b/src/main/java/com/contentstack/cms/package-info.java index 6998a405..24fb2de1 100644 --- a/src/main/java/com/contentstack/cms/package-info.java +++ b/src/main/java/com/contentstack/cms/package-info.java @@ -1,5 +1,6 @@ /** - * This is topmost package that is used to contains all the sub-packages in itself + * This is topmost package that is used to contains all the sub-packages in + * itself * like core, models, organisations, stacks, users and contentstack */ package com.contentstack.cms; diff --git a/src/test/java/com/contentstack/cms/TestClient.java b/src/test/java/com/contentstack/cms/TestClient.java index adcf5be3..37444610 100644 --- a/src/test/java/com/contentstack/cms/TestClient.java +++ b/src/test/java/com/contentstack/cms/TestClient.java @@ -11,14 +11,18 @@ public class TestClient { final static Dotenv env = Dotenv.load(); - public final static String ORGANIZATION_UID = (env.get("organizationUid") != null) ? env.get("organizationUid") : "orgId999999999"; + public final static String ORGANIZATION_UID = (env.get("organizationUid") != null) ? env.get("organizationUid") + : "orgId999999999"; public final static String AUTHTOKEN = (env.get("authToken") != null) ? env.get("authToken") : "auth999999999"; public final static String USER_ID = (env.get("userId") != null) ? env.get("userId") : "c11e668e0295477f"; - public final static String OWNERSHIP = (env.get("ownershipToken") != null) ? env.get("ownershipToken") : "ownershipTokenId"; + public final static String OWNERSHIP = (env.get("ownershipToken") != null) ? env.get("ownershipToken") + : "ownershipTokenId"; public final static String API_KEY = (env.get("apiKey") != null) ? env.get("apiKey") : "apiKey99999999"; - public final static String MANAGEMENT_TOKEN = (env.get("managementToken") != null) ? env.get("managementToken") : "managementToken99999999"; + public final static String MANAGEMENT_TOKEN = (env.get("managementToken") != null) ? env.get("managementToken") + : "managementToken99999999"; - public final static String DEV_HOST = (env.get("dev_host") != null) ? env.get("dev_host") : "***REMOVED***"; + public final static String DEV_HOST = "api.contentstack.io"; + // (env.get("dev_host") != null) ? env.get("dev_host") : "api.contentstack.io"; private static Contentstack instance; private static Stack stackInstance; @@ -37,7 +41,6 @@ public static Contentstack getClient() { return instance; } - public static Contentstack getCustomClient() { if (instance == null) { synchronized (Contentstack.class) { @@ -71,4 +74,3 @@ public static Stack getStack() { } } - diff --git a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java index c90cf03c..a3a77a28 100644 --- a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java +++ b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java @@ -118,7 +118,9 @@ void fetchTest() { Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId?limit=2&skip=2&include_count=false", request.url().toString()); + Assertions.assertEquals( + "https://api.contentstack.io/v3/taxonomies/taxonomyId?limit=2&skip=2&include_count=false", + request.url().toString()); } @Test @@ -149,7 +151,6 @@ void deleteTest() { Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); } - @Test void deleteTestWithHeaders() { taxonomy.clearParams(); @@ -174,7 +175,6 @@ void deleteTestWithHeaders() { Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); } - @Test void createTest() { JSONObject obj = new JSONObject(); @@ -212,7 +212,8 @@ void testCreateTerm() { Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNotNull(request.body()); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/auth999999999/terms", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/auth999999999/terms", + request.url().toString()); } @Test @@ -231,7 +232,8 @@ void testFindTerm() { Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNull(request.body()); Assertions.assertEquals("limit=3", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/" + _uid + "/terms?limit=3", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/" + _uid + "/terms?limit=3", + request.url().toString()); } @Test @@ -250,11 +252,13 @@ void testFetchTerm() { Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNull(request.body()); - Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/auth999999999/terms/auth999999999?include_children_count=false&include_referenced_entries_count=true", request.url().toString()); + Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", + request.url().encodedQuery()); + Assertions.assertEquals( + "https://api.contentstack.io/v3/taxonomies/auth999999999/terms/auth999999999?include_children_count=false&include_referenced_entries_count=true", + request.url().toString()); } - @Test void testTermUpdate() { terms.clearParams(); @@ -302,7 +306,9 @@ void testDescendantsTerm() { Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNull(request.body()); Assertions.assertEquals("include_count=true", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/auth999999999/terms/termId45/descendants?include_count=true", request.url().toString()); + Assertions.assertEquals( + "https://api.contentstack.io/v3/taxonomies/auth999999999/terms/termId45/descendants?include_count=true", + request.url().toString()); } @Test @@ -321,15 +327,18 @@ void testAncestorsTerm() { Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNull(request.body()); - Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/auth999999999/terms/termId45/ancestors?include_children_count=false&include_referenced_entries_count=true", request.url().toString()); + Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", + request.url().encodedQuery()); + Assertions.assertEquals( + "https://api.contentstack.io/v3/taxonomies/auth999999999/terms/termId45/ancestors?include_children_count=false&include_referenced_entries_count=true", + request.url().toString()); } @Test void findTestAPI() throws IOException { Taxonomy taxonomy = new Contentstack.Builder() .setAuthtoken(TestClient.AUTHTOKEN) - .setHost("***REMOVED***") + .setHost("api.contentstack.io") .build() .stack("blt12c1ba95c1b11e88", "") .taxonomy(); From a9a2a6db2a58152eec3460863a289040fad01bde Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Fri, 15 Dec 2023 12:22:40 +0530 Subject: [PATCH 010/197] Team Impl --- .../com/contentstack/cms/Contentstack.java | 48 ++- .../cms/core/AuthInterceptor.java | 9 + .../java/com/contentstack/cms/core/Util.java | 1 + .../contentstack/cms/stack/AliasService.java | 4 + .../com/contentstack/cms/stack/Stack.java | 11 + .../cms/stack/StackRoleMapping.java | 74 ++++ .../com/contentstack/cms/stack/Taxonomy.java | 21 +- .../cms/stack/TaxonomyService.java | 24 +- .../contentstack/cms/stack/TeamService.java | 36 ++ .../com/contentstack/cms/stack/TeamUsers.java | 67 ++++ .../com/contentstack/cms/stack/Teams.java | 89 +++++ .../com/contentstack/cms/stack/Terms.java | 41 ++- .../contentstack/cms/stack/TaxonomyTest.java | 25 +- .../contentstack/cms/stack/TeamsUnitTest.java | 333 ++++++++++++++++++ 14 files changed, 745 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/contentstack/cms/stack/StackRoleMapping.java create mode 100644 src/main/java/com/contentstack/cms/stack/TeamService.java create mode 100644 src/main/java/com/contentstack/cms/stack/TeamUsers.java create mode 100644 src/main/java/com/contentstack/cms/stack/Teams.java create mode 100644 src/test/java/com/contentstack/cms/stack/TeamsUnitTest.java diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index f51c64a5..d5cd049a 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -51,6 +51,7 @@ public class Contentstack { protected final Boolean retryOnFailure; protected final Proxy proxy; protected AuthInterceptor interceptor; + protected String[] earlyAccess; protected User user; /** @@ -80,8 +81,8 @@ public class Contentstack { * @return User * @author ***REMOVED*** * @see User - * + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#users">User + * * @since 2022-05-19 */ public User user() { @@ -130,8 +131,8 @@ public User user() { * @throws IOException the IOException * @author ***REMOVED*** * @see User - * + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#users">User + * */ public Response login(String emailId, String password) throws IOException { if (this.authtoken != null) @@ -183,10 +184,10 @@ public Response login(String emailId, String password) throws IOEx * @throws IOException the IOException * @author ***REMOVED*** * @see Login - * your account - * + * href= + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#log-in-to-your-account">Login + * your account + * */ public Response login(String emailId, String password, String tfaToken) throws IOException { if (this.authtoken != null) @@ -287,11 +288,12 @@ public Organization organization() { * * @param organizationUid The UID of the organization that you want to retrieve * @return the organization - *
    - * Example + *
    + * Example * - *

    +     * 
          *         Contentstack contentstack = new Contentstack.Builder().build();
    +     *         
    * Organization org = contentstack.organization(); *
    */ @@ -447,6 +449,7 @@ public Contentstack(Builder builder) { this.retryOnFailure = builder.retry; this.proxy = builder.proxy; this.interceptor = builder.authInterceptor; + this.earlyAccess = builder.earlyAccess; } /** @@ -461,6 +464,7 @@ public static class Builder { private AuthInterceptor authInterceptor; private String authtoken; // authtoken for client + private String[] earlyAccess; private Retrofit instance; // client instance private String hostname = Util.HOST; // Default Host for Contentstack API (default: api.contentstack.io) private String port = Util.PORT; // Default PORT for Contentstack API @@ -488,14 +492,14 @@ public Builder() { *
    *

    * {@code - * -

    + * + *

    * Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("hostname", 433)); * Contentstack contentstack = new Contentstack.Builder().setProxy(proxy).build(); - * -

    + * + *

    * } - * + * * @param proxy the proxy * @return the Builder instance */ @@ -578,9 +582,9 @@ public Builder setTimeout(int timeout) { * unit of granularity and provides utility methods to * convert across units * @return instance of Builder - *

    - * Example: - * {@code + *

    + * Example: + * {@code * Contentstack cs = new Contentstack.Builder() * .setAuthtoken(AUTHTOKEN) * .setConnectionPool(5, 400, TimeUnit.MILLISECONDS) @@ -604,6 +608,12 @@ public Builder setAuthtoken(String authtoken) { return this; } + + public Builder earlyAccess(String[] earlyAccess) { + this.earlyAccess = earlyAccess; + return this; + } + /** * Build contentstack. * diff --git a/src/main/java/com/contentstack/cms/core/AuthInterceptor.java b/src/main/java/com/contentstack/cms/core/AuthInterceptor.java index 6245de5e..adf49001 100644 --- a/src/main/java/com/contentstack/cms/core/AuthInterceptor.java +++ b/src/main/java/com/contentstack/cms/core/AuthInterceptor.java @@ -25,6 +25,7 @@ public class AuthInterceptor implements Interceptor { protected String authtoken; + protected String[] earlyAccess; // The `public AuthInterceptor() {}` is a default constructor for the // `AuthInterceptor` class. It is @@ -51,6 +52,10 @@ public void setAuthtoken(String authtoken) { this.authtoken = authtoken; } + public void setEarlyAccess(String[] earlyAccess) { + this.earlyAccess = earlyAccess; + } + /** * This function intercepts a request and adds headers to it, including a user * agent, content type, and @@ -73,6 +78,10 @@ public Response intercept(Chain chain) throws IOException { if (this.authtoken != null) { request.addHeader(Util.AUTHTOKEN, this.authtoken); } + if (this.earlyAccess!=null && this.earlyAccess.length > 0) { + String commaSeparated = String.join(", ", earlyAccess); + request.addHeader(Util.EARLY_ACCESS_HEADER, commaSeparated); + } return chain.proceed(request.build()); } diff --git a/src/main/java/com/contentstack/cms/core/Util.java b/src/main/java/com/contentstack/cms/core/Util.java index 38f9db14..48a0fa29 100644 --- a/src/main/java/com/contentstack/cms/core/Util.java +++ b/src/main/java/com/contentstack/cms/core/Util.java @@ -38,6 +38,7 @@ public class Util { public static final String API_KEY = "api_key"; public static final String AUTHORIZATION = "authorization"; public static final String AUTHTOKEN = "authtoken"; + public static final String EARLY_ACCESS_HEADER = "x-header-ea"; public static final String BRANCH = "branch"; public static final String X_USER_AGENT = "X-User-Agent"; public static final String USER_AGENT = "User-Agent"; diff --git a/src/main/java/com/contentstack/cms/stack/AliasService.java b/src/main/java/com/contentstack/cms/stack/AliasService.java index c8f0ec54..e1186f75 100644 --- a/src/main/java/com/contentstack/cms/stack/AliasService.java +++ b/src/main/java/com/contentstack/cms/stack/AliasService.java @@ -7,6 +7,10 @@ import java.util.Map; + +/** + * The interface Alias service. + */ public interface AliasService { @GET("stacks/branch_aliases") diff --git a/src/main/java/com/contentstack/cms/stack/Stack.java b/src/main/java/com/contentstack/cms/stack/Stack.java index 97b38548..1609fd14 100644 --- a/src/main/java/com/contentstack/cms/stack/Stack.java +++ b/src/main/java/com/contentstack/cms/stack/Stack.java @@ -822,6 +822,17 @@ public Alias alias(String aliasUid) { } + /** + * Teams streamline the process of assigning roles and permissions by grouping users together. + * Rather than assigning roles to individual users or at the stack level, you can assign roles directly to a team. This ensures that all users within a team share the same set of role permissions, making role management more efficient. + * + * @return instance of {@link Teams} + */ + public Teams teams() { + return new Teams(this.client, this.headers); + } + + /** * The taxonomy works on data where hierarchy is configured * diff --git a/src/main/java/com/contentstack/cms/stack/StackRoleMapping.java b/src/main/java/com/contentstack/cms/stack/StackRoleMapping.java new file mode 100644 index 00000000..9be7c5cd --- /dev/null +++ b/src/main/java/com/contentstack/cms/stack/StackRoleMapping.java @@ -0,0 +1,74 @@ +package com.contentstack.cms.stack; + +import com.contentstack.cms.BaseImplementation; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; +import org.json.simple.JSONObject; +import retrofit2.Call; + +import java.util.HashMap; + +/** + * Teams streamline the process of assigning roles and permissions by grouping users together. + * Rather than assigning roles to individual users or at the stack level, + * you can assign roles directly to a team. + * This ensures that all users within a team share the same set of role permissions, + * making role management more efficient. + */ +public class StackRoleMapping implements BaseImplementation { + + private HashMap headers; + private HashMap params; + final TeamService teamService; + final String teamId; + + public StackRoleMapping(TeamService service, HashMap headers, @NotNull String teamId) { + this.headers.putAll(headers); + this.params = new HashMap<>(); + this.teamId = teamId; + this.teamService = service; + } + + @Override + public StackRoleMapping addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + @Override + public StackRoleMapping addHeader(@NotNull String key, @NotNull String value) { + this.headers.put(key, value); + return this; + } + + @Override + public StackRoleMapping addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + @Override + public StackRoleMapping addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; + } + + + public Call create(@NotNull JSONObject body) { + return this.teamService.create(this.headers, body); + } + +// public Call find() { +// return this.teamService.find(this.headers, body); +// } +// +// public Call update(@NotNull String stackApiKey, @NotNull JSONObject body) { +// return this.teamService.update(this.headers, this.teamId, stackApiKey, body); +// } +// +// public Call delete(@NotNull String stackApiKey) { +// return this.teamService.delete(this.headers, this.teamId, stackApiKey); +// } + + +} diff --git a/src/main/java/com/contentstack/cms/stack/Taxonomy.java b/src/main/java/com/contentstack/cms/stack/Taxonomy.java index 65f1fb0c..020270e8 100644 --- a/src/main/java/com/contentstack/cms/stack/Taxonomy.java +++ b/src/main/java/com/contentstack/cms/stack/Taxonomy.java @@ -11,7 +11,11 @@ /** - * The type Taxonomy. + * Taxonomy is a system that classifies and organizes content in your collection or system. + * It simplifies finding and retrieving information by categorizing items based on specific + * criteria like their purpose, audience, or other relevant factors. + *

    + * This hierarchical organization streamlines navigation and search processes. */ public class Taxonomy implements BaseImplementation { @@ -147,7 +151,7 @@ public Taxonomy addParams(@NotNull HashMap params) { * *

* - * @return the call

Example
     {@code
+     * @return the call Example 
     {@code
      *     Response response = taxonomy.find().execute();
      *     } 
*/ @@ -172,7 +176,7 @@ public Call find() { * * * @param taxonomyId the taxonomy id - * @return the call

Example
     {@code
+     * @return the call Example 
     {@code
      *     Response response = taxonomy.fetch("taxonomyId").execute();
      *     } 
*/ @@ -184,7 +188,7 @@ public Call fetch(@NotNull String taxonomyId) { * Create Taxonomy call. * * @param body the body - * @return the call

Example
     {@code
+     * @return the call Example 
     {@code
      *     JSONObject body = new JSONObject
      *     Response response = taxonomy.create(body).execute();
      *     } 
@@ -198,7 +202,7 @@ public Call create(@NotNull JSONObject body) { * * @param taxonomyId - The taxonomy for which we need to update the details * @param body the body - * @return the call

Example
     {@code
+     * @return the call Example 
     {@code
      *     JSONObject body = new JSONObject();
      *     JSONObject bodyContent = new JSONObject();
      *     bodyContent.put("name", "Taxonomy 1");
@@ -215,7 +219,7 @@ public Call update(@NotNull String taxonomyId, @NotNull JSONObject
      * Delete Taxonomy call.
      *
      * @param taxonomyId - The taxonomy for which we need to update the details
-     * @return the call 

Example
     {@code
+     * @return the call Example 
     {@code
      *     Response response = taxonomy.delete("taxonomyId").execute();
      *     } 
*/ @@ -223,6 +227,7 @@ public Call delete(@NotNull String taxonomyId) { return this.taxonomyService.delete(this.headers, taxonomyId); } + /** * Clear params for internal uses only for testing */ @@ -233,12 +238,11 @@ protected void clearParams() { /** * Get terms information - *

*

Examples

*
      *     {@code
      *     Term terms = stack("authtoken").taxonomy("taxonomyId").term();
-     *     }*
+     *     }
      * 
* * @return instance of {@link Terms} @@ -247,5 +251,4 @@ public Terms terms() { return new Terms(this.taxonomyService, this.headers, this.taxonomyId); } - } diff --git a/src/main/java/com/contentstack/cms/stack/TaxonomyService.java b/src/main/java/com/contentstack/cms/stack/TaxonomyService.java index aebf2cb7..f104c368 100644 --- a/src/main/java/com/contentstack/cms/stack/TaxonomyService.java +++ b/src/main/java/com/contentstack/cms/stack/TaxonomyService.java @@ -11,10 +11,15 @@ public interface TaxonomyService { @GET("taxonomies") - Call find(@HeaderMap Map headers, @QueryMap Map params); + Call find( + @HeaderMap Map headers, + @QueryMap Map params); @GET("taxonomies/{taxonomy_uid}") - Call fetch(@HeaderMap Map headers, @Path("taxonomy_uid") String uid, @QueryMap Map query); + Call fetch( + @HeaderMap Map headers, + @Path("taxonomy_uid") String uid, + @QueryMap Map query); @POST("taxonomies") Call create( @@ -32,7 +37,8 @@ Call delete( @HeaderMap Map headers, @Path("taxonomy_uid") String uid); - //--Terms-- + + // --Terms-- @POST("taxonomies/{taxonomy_uid}/terms") Call createTerm( @HeaderMap HashMap headers, @@ -73,10 +79,16 @@ Call updateTerm( @Path("term_id") String termId, @Body JSONObject body); + @PUT("taxonomies/{taxonomy_uid}/terms/{term_id}/move") + Call reorder( + @HeaderMap HashMap headers, + @Path("taxonomy_uid") String taxonomyId, + @Path("term_id") String termId, + @QueryMap Map queryParams, + @Body JSONObject body); - @GET("taxonomies/all/terms") + @GET("taxonomies/$all/terms") Call searchTerm( @HeaderMap HashMap headers, - @Query("term") String termString - ); + @Query("typeahead") String termString); } diff --git a/src/main/java/com/contentstack/cms/stack/TeamService.java b/src/main/java/com/contentstack/cms/stack/TeamService.java new file mode 100644 index 00000000..7896caf9 --- /dev/null +++ b/src/main/java/com/contentstack/cms/stack/TeamService.java @@ -0,0 +1,36 @@ +package com.contentstack.cms.stack; + +import okhttp3.ResponseBody; +import org.json.simple.JSONObject; +import retrofit2.Call; +import retrofit2.http.*; + +import java.util.Map; + +/** + * Allows all the methods related to Team API members + */ +public interface TeamService { + + @GET("teams") + Call find( + @HeaderMap Map headers, + @QueryMap Map params); + + @POST("teams") + Call create( + @HeaderMap Map headers, + @QueryMap Map params); + + @GET("teams/{teamId}") + Call fetch( + @HeaderMap Map headers, + @QueryMap Map params); + + @PUT("teams/{teamId}") + Call update( + @HeaderMap Map headers, + @Path("teamId") String uid, + @Body JSONObject body); + +} diff --git a/src/main/java/com/contentstack/cms/stack/TeamUsers.java b/src/main/java/com/contentstack/cms/stack/TeamUsers.java new file mode 100644 index 00000000..0574a2f7 --- /dev/null +++ b/src/main/java/com/contentstack/cms/stack/TeamUsers.java @@ -0,0 +1,67 @@ +package com.contentstack.cms.stack; + +import com.contentstack.cms.BaseImplementation; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; +import org.json.simple.JSONObject; +import retrofit2.Call; + +import java.util.HashMap; + +/** + * Teams streamline the process of assigning roles and permissions by grouping users together. + * Rather than assigning roles to individual users or at the stack level, + * you can assign roles directly to a team. + * This ensures that all users within a team share the same set of role permissions, + * making role management more efficient. + */ +public class TeamUsers implements BaseImplementation { + + private HashMap headers; + private HashMap params; + final TeamService teamService; + + public TeamUsers(TeamService service, HashMap headers, @NotNull String teamId) { + this.headers.putAll(headers); + this.params = new HashMap<>(); + this.teamService = service; + } + + @Override + public TeamUsers addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + @Override + public TeamUsers addHeader(@NotNull String key, @NotNull String value) { + this.headers.put(key, value); + return this; + } + + @Override + public TeamUsers addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + @Override + public TeamUsers addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; + } + + + public Call create(@NotNull JSONObject body) { + return this.teamService.create(this.headers, body); + } + +// public Call find() { +// return this.teamService.find(this.headers, body); +// } +// +// public Call delete(@NotNull String teamId) { +// return this.teamService.delete(this.headers, teamId); +// } + +} diff --git a/src/main/java/com/contentstack/cms/stack/Teams.java b/src/main/java/com/contentstack/cms/stack/Teams.java new file mode 100644 index 00000000..1cf40e44 --- /dev/null +++ b/src/main/java/com/contentstack/cms/stack/Teams.java @@ -0,0 +1,89 @@ +package com.contentstack.cms.stack; + +import com.contentstack.cms.BaseImplementation; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; +import org.json.simple.JSONObject; +import retrofit2.Call; +import retrofit2.Retrofit; + +import java.util.HashMap; + +/** + * Teams streamline the process of assigning roles and permissions by grouping users together. + * Rather than assigning roles to individual users or at the stack level, + * you can assign roles directly to a team. + * This ensures that all users within a team share the same set of role permissions, + * making role management more efficient. + */ +public class Teams implements BaseImplementation { + + private HashMap headers; + private HashMap params; + final TeamService teamService; + + public Teams(Retrofit client, HashMap headers) { + this.headers.putAll(headers); + this.params = new HashMap<>(); + this.teamService = client.create(TeamService.class); + } + + @Override + public Teams addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + @Override + public Teams addHeader(@NotNull String key, @NotNull String value) { + this.headers.put(key, value); + return this; + } + + @Override + public Teams addParams(@NotNull HashMap params) { + this.params.putAll(params); + return this; + } + + @Override + public Teams addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; + } + + +// /** +// * +// * @return Call that conatains response body +// */ +// public Call find() { +// return this.teamService.find(this.headers, this.params); +// } +// +// public Call create(@NotNull JSONObject body) { +// return this.teamService.create(this.headers, body); +// } +// +// public Call fetch(@NotNull String teamId) { +// return this.teamService.fetch(this.headers, teamId); +// } +// +// public Call update(@NotNull String teamId, @NotNull JSONObject body) { +// return this.teamService.update(this.headers, teamId, body); +// } +// +// public Call delete(@NotNull String teamId) { +// return this.teamService.delete(this.headers, teamId); +// } +// +// public TeamUsers users(@NotNull String teamId) { +// return new TeamUsers(this.teamService, this.headers, teamId); +// } +// +// public StackRoleMapping stackRoleMapping(@NotNull String teamId) { +// return new StackRoleMapping(this.teamService, this.headers, teamId); +// } + + +} diff --git a/src/main/java/com/contentstack/cms/stack/Terms.java b/src/main/java/com/contentstack/cms/stack/Terms.java index 61e09a09..9ab07985 100644 --- a/src/main/java/com/contentstack/cms/stack/Terms.java +++ b/src/main/java/com/contentstack/cms/stack/Terms.java @@ -9,7 +9,8 @@ import java.util.HashMap; /** - * provides an implementation + * Terms are the fundamental building blocks in a taxonomy. + * They are used to create hierarchical structures and are integrated into entries to classify and categorize information systematically. *
    *
  • Create a Term
  • *
  • Get all Terms of a Taxonomy
  • @@ -238,6 +239,44 @@ public Call update(@NotNull String termUid, @NotNull JSONObject bo } + /** + * @param termUid The term which we need to move(change the parent) + * @param body the request body + * + * //At Root Level: + * { + * "term": { + * "parent_uid": null, + * "order": 2 + * } + * } + *

    + * //Under an existing Term on a different level: + * { + * "term": { + * "parent_uid": "term_1", + * "order": 5 + * } + * } + *

    + * //Under an existing Term on the same level(Reorder Term): + * { + * "term": { + * "parent_uid": "term_3", + * "order": 1 + * } + * } + * @return Call + * @see #addParam(String, Object) to provide additional params for below : + *

    force - It’s set to false by default, which will give an error if we + * try to move a term which has got child terms within it, when set to true, + * it’ll force move the term(which will also affect the ancestors hierarchy of all it’s child terms) + */ + public Call reorder(@NotNull String termUid, @NotNull JSONObject body) { + return this.taxonomyService.reorder(this.headers, this.taxonomyId, termUid, this.params, body); + } + + /** * Search call. * diff --git a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java index a3a77a28..d65d7c3b 100644 --- a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java +++ b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java @@ -201,7 +201,7 @@ void testCreateTerm() { term.put("term", termDetails); term.put("parent_uid", null); Request request = terms.create(term).request(); - Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -221,7 +221,7 @@ void testFindTerm() { terms.clearParams(); terms.addParam("limit", 3); Request request = terms.find().request(); - Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -279,11 +279,30 @@ void testTermUpdate() { Assertions.assertEquals(5, request.url().pathSegments().size()); } + + @Test + void testTermMoveOrReorder() { + terms.clearParams(); + HashMap headers = new HashMap<>(); + HashMap params = new HashMap<>(); + terms.addHeader("Accept-Encoding", "UTF-8"); + headers.put("Accept-Encoding", "UTF-8"); + params.put("force", true); + terms.addParams(params); + terms.addHeaders(headers); + Request request = terms.reorder(_uid, new JSONObject()).request(); + Assertions.assertEquals(3, request.headers().names().size()); + Assertions.assertEquals("PUT", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(6, request.url().pathSegments().size()); + } + @Test void testTermSearch() { terms.clearParams(); Request request = terms.search("contentstack").request(); - Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); diff --git a/src/test/java/com/contentstack/cms/stack/TeamsUnitTest.java b/src/test/java/com/contentstack/cms/stack/TeamsUnitTest.java new file mode 100644 index 00000000..1992f132 --- /dev/null +++ b/src/test/java/com/contentstack/cms/stack/TeamsUnitTest.java @@ -0,0 +1,333 @@ +package com.contentstack.cms.stack; + +import com.contentstack.cms.TestClient; +import org.json.simple.JSONObject; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.TestMethodOrder; + +@Tag("unit") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class TeamsUnitTest { + + protected static String API_KEY = TestClient.API_KEY; + protected static String _uid = TestClient.AUTHTOKEN; + protected static String MANAGEMENT_TOKEN = TestClient.MANAGEMENT_TOKEN; + protected static Teams teams; + protected static JSONObject body; + + // Create a JSONObject, JSONObject could be created in multiple ways. + // We choose JSONParser that converts string to JSONObject + String theBody = "{\n" + + " \"taxonomy\": {\n" + + " \"name\": \"Taxonomy 1\",\n" + + " \"description\": \"Description updated for Taxonomy 1\"\n" + + " }\n" + + "}"; + +// @BeforeAll +// static void setup() { +// final String AUTHTOKEN = TestClient.AUTHTOKEN; +// HashMap headers = new HashMap<>(); +// headers.put(Util.API_KEY, API_KEY); +// headers.put(Util.AUTHORIZATION, MANAGEMENT_TOKEN); +// Stack stack = new Contentstack.Builder().setAuthtoken(AUTHTOKEN).build().stack(headers); +// teams = stack.teams(); +// +// +// } +// +// @Test +// void findTest() { +// Request request = teams.find().request(); +// Assertions.assertEquals(2, request.headers().names().size()); +// Assertions.assertEquals("GET", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(2, request.url().pathSegments().size()); +// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); +// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); +// Assertions.assertNotNull(request.url().encodedQuery()); +// Assertions.assertEquals( +// "https://api.contentstack.io/v3/taxonomies?include_rules=true&include_permissions=true", +// request.url().toString()); +// } +// +// @Test +// void findTestWithParams() { +// teams.addParam("limit", 2); +// teams.addParam("skip", 2); +// teams.addParam("include_count", false); +// Request request = teams.find().request(); +// Assertions.assertEquals(2, request.headers().names().size()); +// Assertions.assertEquals("GET", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(2, request.url().pathSegments().size()); +// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); +// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); +// Assertions.assertNotNull(request.url().encodedQuery()); +// Assertions.assertEquals( +// "https://api.contentstack.io/v3/taxonomies?limit=2&skip=2&include_count=false", +// request.url().toString()); +// } +// +// @Test +// void findTestCustomParams() { +// teams.clearParams(); +// teams.addParam("include_rules", true); +// teams.addParam("include_permissions", true); +// Request request = teams.find().request(); +// Assertions.assertEquals(2, request.headers().names().size()); +// Assertions.assertEquals("GET", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(2, request.url().pathSegments().size()); +// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); +// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); +// Assertions.assertNotNull(request.url().encodedQuery()); +// Assertions.assertEquals( +// "https://api.contentstack.io/v3/taxonomies?include_rules=true&include_permissions=true", +// request.url().toString()); +// } +// +// @Test +// void fetchTest() { +// Request request = teams.fetch("taxonomyId").request(); +// Assertions.assertEquals(2, request.headers().names().size()); +// Assertions.assertEquals("GET", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(3, request.url().pathSegments().size()); +// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); +// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); +// Assertions.assertNotNull(request.url().encodedQuery()); +// Assertions.assertEquals( +// "https://api.contentstack.io/v3/taxonomies/taxonomyId?limit=2&skip=2&include_count=false", +// request.url().toString()); +// } +// +// @Test +// void updateTest() { +// Request request = teams.update("taxonomyId", body).request(); +// Assertions.assertEquals(2, request.headers().names().size()); +// Assertions.assertEquals("PUT", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(3, request.url().pathSegments().size()); +// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); +// Assertions.assertNotNull(request.body()); +// Assertions.assertNull(request.url().encodedQuery()); +// Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); +// } +// +// @Test +// void deleteTest() { +// Request request = teams.delete("taxonomyId").request(); +// Assertions.assertEquals(5, request.headers().names().size()); +// Assertions.assertEquals("DELETE", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(3, request.url().pathSegments().size()); +// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); +// Assertions.assertNull(request.body()); +// Assertions.assertNull(request.url().encodedQuery()); +// Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); +// } +// +// @Test +// void deleteTestWithHeaders() { +// teams.clearParams(); +// teams.addHeader("Content-Type", "application/json"); +// HashMap headers = new HashMap<>(); +// HashMap params = new HashMap<>(); +// headers.put("key_param1", "key_param_value"); +// headers.put("key_param2", "key_param_value"); +// params.put("key_param3", "key_param_value"); +// params.put("key_param4", "key_param_value"); +// teams.addHeaders(headers); +// teams.addParams(params); +// Request request = teams.delete("taxonomyId").request(); +// Assertions.assertEquals(5, request.headers().names().size()); +// Assertions.assertEquals("DELETE", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(3, request.url().pathSegments().size()); +// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); +// Assertions.assertNull(request.body()); +// Assertions.assertNull(request.url().encodedQuery()); +// Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); +// } +// +// @Test +// void createTest() { +// JSONObject obj = new JSONObject(); +// obj.put("name", "sample"); +// Request request = teams.create(obj).request(); +// Assertions.assertEquals(4, request.headers().names().size()); +// Assertions.assertEquals("POST", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(2, request.url().pathSegments().size()); +// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); +// Assertions.assertNull(request.url().encodedQuery()); +// Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies", request.url().toString()); +// } +// +// @Test +// void testCreateTerm() { +// terms.clearParams(); +// JSONObject term = new JSONObject(); +// JSONObject termDetails = new JSONObject(); +// termDetails.put("uid", "term_1"); +// termDetails.put("name", "Term 1"); +// termDetails.put("description", "Term 1 Description for Taxonomy 1"); +// term.put("term", termDetails); +// term.put("parent_uid", null); +// Request request = terms.create(term).request(); +// Assertions.assertEquals(2, request.headers().names().size()); +// Assertions.assertEquals("POST", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(4, request.url().pathSegments().size()); +// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); +// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); +// Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); +// Assertions.assertEquals("terms", request.url().pathSegments().get(3)); +// Assertions.assertNotNull(request.body()); +// Assertions.assertNull(request.url().encodedQuery()); +// Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/auth999999999/terms", +// request.url().toString()); +// } +// +// @Test +// void testFindTerm() { +// terms.clearParams(); +// terms.addParam("limit", 3); +// Request request = terms.find().request(); +// Assertions.assertEquals(2, request.headers().names().size()); +// Assertions.assertEquals("GET", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(4, request.url().pathSegments().size()); +// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); +// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); +// Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); +// Assertions.assertEquals("terms", request.url().pathSegments().get(3)); +// Assertions.assertNull(request.body()); +// Assertions.assertEquals("limit=3", request.url().encodedQuery()); +// Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/" + _uid + "/terms?limit=3", +// request.url().toString()); +// } +// +// @Test +// void testFetchTerm() { +// terms.clearParams(); +// terms.addParam("include_referenced_entries_count", true); +// terms.addParam("include_children_count", false); +// Request request = terms.fetch(_uid).request(); +// Assertions.assertEquals(2, request.headers().names().size()); +// Assertions.assertEquals("GET", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(5, request.url().pathSegments().size()); +// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); +// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); +// Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); +// Assertions.assertEquals("terms", request.url().pathSegments().get(3)); +// Assertions.assertNull(request.body()); +// Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", +// request.url().encodedQuery()); +// Assertions.assertEquals( +// "https://api.contentstack.io/v3/taxonomies/auth999999999/terms/auth999999999?include_children_count=false&include_referenced_entries_count=true", +// request.url().toString()); +// } +// +// @Test +// void testTermUpdate() { +// terms.clearParams(); +// HashMap headers = new HashMap<>(); +// HashMap params = new HashMap<>(); +// terms.addParam("include_referenced_entries_count", true); +// terms.addParam("include_children_count", false); +// terms.addHeader("Accept-Encoding", "UTF-8"); +// headers.put("Accept-Encoding", "UTF-8"); +// params.put("include_children_count", "true"); +// terms.addParams(params); +// terms.addHeaders(headers); +// Request request = terms.update(_uid, new JSONObject()).request(); +// Assertions.assertEquals(3, request.headers().names().size()); +// Assertions.assertEquals("PUT", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(5, request.url().pathSegments().size()); +// } +// +// @Test +// void testTermSearch() { +// terms.clearParams(); +// Request request = terms.search("contentstack").request(); +// Assertions.assertEquals(2, request.headers().names().size()); +// Assertions.assertEquals("GET", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(4, request.url().pathSegments().size()); +// } +// +// @Test +// void testDescendantsTerm() { +// terms.clearParams(); +// terms.addParam("include_count", true); +// Request request = terms.descendants("termId45").request(); +// Assertions.assertEquals(2, request.headers().names().size()); +// Assertions.assertEquals("GET", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(6, request.url().pathSegments().size()); +// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); +// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); +// Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); +// Assertions.assertEquals("terms", request.url().pathSegments().get(3)); +// Assertions.assertNull(request.body()); +// Assertions.assertEquals("include_count=true", request.url().encodedQuery()); +// Assertions.assertEquals( +// "https://api.contentstack.io/v3/taxonomies/auth999999999/terms/termId45/descendants?include_count=true", +// request.url().toString()); +// } +// +// @Test +// void testAncestorsTerm() { +// terms.clearParams(); +// terms.addParam("include_referenced_entries_count", true); +// terms.addParam("include_children_count", false); +// Request request = terms.ancestors("termId45").request(); +// Assertions.assertEquals(3, request.headers().names().size()); +// Assertions.assertEquals("GET", request.method()); +// Assertions.assertTrue(request.url().isHttps()); +// Assertions.assertEquals("api.contentstack.io", request.url().host()); +// Assertions.assertEquals(6, request.url().pathSegments().size()); +// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); +// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); +// Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); +// Assertions.assertEquals("terms", request.url().pathSegments().get(3)); +// Assertions.assertNull(request.body()); +// Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", +// request.url().encodedQuery()); +// Assertions.assertEquals( +// "https://api.contentstack.io/v3/taxonomies/auth999999999/terms/termId45/ancestors?include_children_count=false&include_referenced_entries_count=true", +// request.url().toString()); +// } +// +// @Test +// void findTestAPI() throws IOException { +// Taxonomy taxonomy = new Contentstack.Builder() +// .setAuthtoken(TestClient.AUTHTOKEN) +// .setHost("api.contentstack.io") +// .build() +// .stack("blt12c1ba95c1b11e88", "") +// .taxonomy(); +// Response response = taxonomy.addHeader("authtoken", "blt67b95aeb964f5262").find().execute(); +// System.out.println(response); +// } + +} From ff2a72b85cae9366cf11a7c1c95510f19518fe5e Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Fri, 15 Dec 2023 13:11:08 +0530 Subject: [PATCH 011/197] CS-42937 : Support for Early Access Header CS-42936 : Taxonomy document and Test --- .../com/contentstack/cms/stack/Stack.java | 11 - .../cms/stack/StackRoleMapping.java | 74 ---- .../contentstack/cms/stack/TeamService.java | 36 -- .../com/contentstack/cms/stack/TeamUsers.java | 67 ---- .../com/contentstack/cms/stack/Teams.java | 89 ----- .../contentstack/cms/stack/TeamsUnitTest.java | 333 ------------------ 6 files changed, 610 deletions(-) delete mode 100644 src/main/java/com/contentstack/cms/stack/StackRoleMapping.java delete mode 100644 src/main/java/com/contentstack/cms/stack/TeamService.java delete mode 100644 src/main/java/com/contentstack/cms/stack/TeamUsers.java delete mode 100644 src/main/java/com/contentstack/cms/stack/Teams.java delete mode 100644 src/test/java/com/contentstack/cms/stack/TeamsUnitTest.java diff --git a/src/main/java/com/contentstack/cms/stack/Stack.java b/src/main/java/com/contentstack/cms/stack/Stack.java index 1609fd14..97b38548 100644 --- a/src/main/java/com/contentstack/cms/stack/Stack.java +++ b/src/main/java/com/contentstack/cms/stack/Stack.java @@ -822,17 +822,6 @@ public Alias alias(String aliasUid) { } - /** - * Teams streamline the process of assigning roles and permissions by grouping users together. - * Rather than assigning roles to individual users or at the stack level, you can assign roles directly to a team. This ensures that all users within a team share the same set of role permissions, making role management more efficient. - * - * @return instance of {@link Teams} - */ - public Teams teams() { - return new Teams(this.client, this.headers); - } - - /** * The taxonomy works on data where hierarchy is configured * diff --git a/src/main/java/com/contentstack/cms/stack/StackRoleMapping.java b/src/main/java/com/contentstack/cms/stack/StackRoleMapping.java deleted file mode 100644 index 9be7c5cd..00000000 --- a/src/main/java/com/contentstack/cms/stack/StackRoleMapping.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.contentstack.cms.stack; - -import com.contentstack.cms.BaseImplementation; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; -import org.json.simple.JSONObject; -import retrofit2.Call; - -import java.util.HashMap; - -/** - * Teams streamline the process of assigning roles and permissions by grouping users together. - * Rather than assigning roles to individual users or at the stack level, - * you can assign roles directly to a team. - * This ensures that all users within a team share the same set of role permissions, - * making role management more efficient. - */ -public class StackRoleMapping implements BaseImplementation { - - private HashMap headers; - private HashMap params; - final TeamService teamService; - final String teamId; - - public StackRoleMapping(TeamService service, HashMap headers, @NotNull String teamId) { - this.headers.putAll(headers); - this.params = new HashMap<>(); - this.teamId = teamId; - this.teamService = service; - } - - @Override - public StackRoleMapping addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); - return this; - } - - @Override - public StackRoleMapping addHeader(@NotNull String key, @NotNull String value) { - this.headers.put(key, value); - return this; - } - - @Override - public StackRoleMapping addParams(@NotNull HashMap params) { - this.params.putAll(params); - return this; - } - - @Override - public StackRoleMapping addHeaders(@NotNull HashMap headers) { - this.headers.putAll(headers); - return this; - } - - - public Call create(@NotNull JSONObject body) { - return this.teamService.create(this.headers, body); - } - -// public Call find() { -// return this.teamService.find(this.headers, body); -// } -// -// public Call update(@NotNull String stackApiKey, @NotNull JSONObject body) { -// return this.teamService.update(this.headers, this.teamId, stackApiKey, body); -// } -// -// public Call delete(@NotNull String stackApiKey) { -// return this.teamService.delete(this.headers, this.teamId, stackApiKey); -// } - - -} diff --git a/src/main/java/com/contentstack/cms/stack/TeamService.java b/src/main/java/com/contentstack/cms/stack/TeamService.java deleted file mode 100644 index 7896caf9..00000000 --- a/src/main/java/com/contentstack/cms/stack/TeamService.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.contentstack.cms.stack; - -import okhttp3.ResponseBody; -import org.json.simple.JSONObject; -import retrofit2.Call; -import retrofit2.http.*; - -import java.util.Map; - -/** - * Allows all the methods related to Team API members - */ -public interface TeamService { - - @GET("teams") - Call find( - @HeaderMap Map headers, - @QueryMap Map params); - - @POST("teams") - Call create( - @HeaderMap Map headers, - @QueryMap Map params); - - @GET("teams/{teamId}") - Call fetch( - @HeaderMap Map headers, - @QueryMap Map params); - - @PUT("teams/{teamId}") - Call update( - @HeaderMap Map headers, - @Path("teamId") String uid, - @Body JSONObject body); - -} diff --git a/src/main/java/com/contentstack/cms/stack/TeamUsers.java b/src/main/java/com/contentstack/cms/stack/TeamUsers.java deleted file mode 100644 index 0574a2f7..00000000 --- a/src/main/java/com/contentstack/cms/stack/TeamUsers.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.contentstack.cms.stack; - -import com.contentstack.cms.BaseImplementation; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; -import org.json.simple.JSONObject; -import retrofit2.Call; - -import java.util.HashMap; - -/** - * Teams streamline the process of assigning roles and permissions by grouping users together. - * Rather than assigning roles to individual users or at the stack level, - * you can assign roles directly to a team. - * This ensures that all users within a team share the same set of role permissions, - * making role management more efficient. - */ -public class TeamUsers implements BaseImplementation { - - private HashMap headers; - private HashMap params; - final TeamService teamService; - - public TeamUsers(TeamService service, HashMap headers, @NotNull String teamId) { - this.headers.putAll(headers); - this.params = new HashMap<>(); - this.teamService = service; - } - - @Override - public TeamUsers addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); - return this; - } - - @Override - public TeamUsers addHeader(@NotNull String key, @NotNull String value) { - this.headers.put(key, value); - return this; - } - - @Override - public TeamUsers addParams(@NotNull HashMap params) { - this.params.putAll(params); - return this; - } - - @Override - public TeamUsers addHeaders(@NotNull HashMap headers) { - this.headers.putAll(headers); - return this; - } - - - public Call create(@NotNull JSONObject body) { - return this.teamService.create(this.headers, body); - } - -// public Call find() { -// return this.teamService.find(this.headers, body); -// } -// -// public Call delete(@NotNull String teamId) { -// return this.teamService.delete(this.headers, teamId); -// } - -} diff --git a/src/main/java/com/contentstack/cms/stack/Teams.java b/src/main/java/com/contentstack/cms/stack/Teams.java deleted file mode 100644 index 1cf40e44..00000000 --- a/src/main/java/com/contentstack/cms/stack/Teams.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.contentstack.cms.stack; - -import com.contentstack.cms.BaseImplementation; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; -import org.json.simple.JSONObject; -import retrofit2.Call; -import retrofit2.Retrofit; - -import java.util.HashMap; - -/** - * Teams streamline the process of assigning roles and permissions by grouping users together. - * Rather than assigning roles to individual users or at the stack level, - * you can assign roles directly to a team. - * This ensures that all users within a team share the same set of role permissions, - * making role management more efficient. - */ -public class Teams implements BaseImplementation { - - private HashMap headers; - private HashMap params; - final TeamService teamService; - - public Teams(Retrofit client, HashMap headers) { - this.headers.putAll(headers); - this.params = new HashMap<>(); - this.teamService = client.create(TeamService.class); - } - - @Override - public Teams addParam(@NotNull String key, @NotNull Object value) { - this.params.put(key, value); - return this; - } - - @Override - public Teams addHeader(@NotNull String key, @NotNull String value) { - this.headers.put(key, value); - return this; - } - - @Override - public Teams addParams(@NotNull HashMap params) { - this.params.putAll(params); - return this; - } - - @Override - public Teams addHeaders(@NotNull HashMap headers) { - this.headers.putAll(headers); - return this; - } - - -// /** -// * -// * @return Call that conatains response body -// */ -// public Call find() { -// return this.teamService.find(this.headers, this.params); -// } -// -// public Call create(@NotNull JSONObject body) { -// return this.teamService.create(this.headers, body); -// } -// -// public Call fetch(@NotNull String teamId) { -// return this.teamService.fetch(this.headers, teamId); -// } -// -// public Call update(@NotNull String teamId, @NotNull JSONObject body) { -// return this.teamService.update(this.headers, teamId, body); -// } -// -// public Call delete(@NotNull String teamId) { -// return this.teamService.delete(this.headers, teamId); -// } -// -// public TeamUsers users(@NotNull String teamId) { -// return new TeamUsers(this.teamService, this.headers, teamId); -// } -// -// public StackRoleMapping stackRoleMapping(@NotNull String teamId) { -// return new StackRoleMapping(this.teamService, this.headers, teamId); -// } - - -} diff --git a/src/test/java/com/contentstack/cms/stack/TeamsUnitTest.java b/src/test/java/com/contentstack/cms/stack/TeamsUnitTest.java deleted file mode 100644 index 1992f132..00000000 --- a/src/test/java/com/contentstack/cms/stack/TeamsUnitTest.java +++ /dev/null @@ -1,333 +0,0 @@ -package com.contentstack.cms.stack; - -import com.contentstack.cms.TestClient; -import org.json.simple.JSONObject; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.TestMethodOrder; - -@Tag("unit") -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class TeamsUnitTest { - - protected static String API_KEY = TestClient.API_KEY; - protected static String _uid = TestClient.AUTHTOKEN; - protected static String MANAGEMENT_TOKEN = TestClient.MANAGEMENT_TOKEN; - protected static Teams teams; - protected static JSONObject body; - - // Create a JSONObject, JSONObject could be created in multiple ways. - // We choose JSONParser that converts string to JSONObject - String theBody = "{\n" + - " \"taxonomy\": {\n" + - " \"name\": \"Taxonomy 1\",\n" + - " \"description\": \"Description updated for Taxonomy 1\"\n" + - " }\n" + - "}"; - -// @BeforeAll -// static void setup() { -// final String AUTHTOKEN = TestClient.AUTHTOKEN; -// HashMap headers = new HashMap<>(); -// headers.put(Util.API_KEY, API_KEY); -// headers.put(Util.AUTHORIZATION, MANAGEMENT_TOKEN); -// Stack stack = new Contentstack.Builder().setAuthtoken(AUTHTOKEN).build().stack(headers); -// teams = stack.teams(); -// -// -// } -// -// @Test -// void findTest() { -// Request request = teams.find().request(); -// Assertions.assertEquals(2, request.headers().names().size()); -// Assertions.assertEquals("GET", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(2, request.url().pathSegments().size()); -// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); -// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); -// Assertions.assertNotNull(request.url().encodedQuery()); -// Assertions.assertEquals( -// "https://api.contentstack.io/v3/taxonomies?include_rules=true&include_permissions=true", -// request.url().toString()); -// } -// -// @Test -// void findTestWithParams() { -// teams.addParam("limit", 2); -// teams.addParam("skip", 2); -// teams.addParam("include_count", false); -// Request request = teams.find().request(); -// Assertions.assertEquals(2, request.headers().names().size()); -// Assertions.assertEquals("GET", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(2, request.url().pathSegments().size()); -// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); -// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); -// Assertions.assertNotNull(request.url().encodedQuery()); -// Assertions.assertEquals( -// "https://api.contentstack.io/v3/taxonomies?limit=2&skip=2&include_count=false", -// request.url().toString()); -// } -// -// @Test -// void findTestCustomParams() { -// teams.clearParams(); -// teams.addParam("include_rules", true); -// teams.addParam("include_permissions", true); -// Request request = teams.find().request(); -// Assertions.assertEquals(2, request.headers().names().size()); -// Assertions.assertEquals("GET", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(2, request.url().pathSegments().size()); -// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); -// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); -// Assertions.assertNotNull(request.url().encodedQuery()); -// Assertions.assertEquals( -// "https://api.contentstack.io/v3/taxonomies?include_rules=true&include_permissions=true", -// request.url().toString()); -// } -// -// @Test -// void fetchTest() { -// Request request = teams.fetch("taxonomyId").request(); -// Assertions.assertEquals(2, request.headers().names().size()); -// Assertions.assertEquals("GET", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(3, request.url().pathSegments().size()); -// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); -// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); -// Assertions.assertNotNull(request.url().encodedQuery()); -// Assertions.assertEquals( -// "https://api.contentstack.io/v3/taxonomies/taxonomyId?limit=2&skip=2&include_count=false", -// request.url().toString()); -// } -// -// @Test -// void updateTest() { -// Request request = teams.update("taxonomyId", body).request(); -// Assertions.assertEquals(2, request.headers().names().size()); -// Assertions.assertEquals("PUT", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(3, request.url().pathSegments().size()); -// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); -// Assertions.assertNotNull(request.body()); -// Assertions.assertNull(request.url().encodedQuery()); -// Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); -// } -// -// @Test -// void deleteTest() { -// Request request = teams.delete("taxonomyId").request(); -// Assertions.assertEquals(5, request.headers().names().size()); -// Assertions.assertEquals("DELETE", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(3, request.url().pathSegments().size()); -// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); -// Assertions.assertNull(request.body()); -// Assertions.assertNull(request.url().encodedQuery()); -// Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); -// } -// -// @Test -// void deleteTestWithHeaders() { -// teams.clearParams(); -// teams.addHeader("Content-Type", "application/json"); -// HashMap headers = new HashMap<>(); -// HashMap params = new HashMap<>(); -// headers.put("key_param1", "key_param_value"); -// headers.put("key_param2", "key_param_value"); -// params.put("key_param3", "key_param_value"); -// params.put("key_param4", "key_param_value"); -// teams.addHeaders(headers); -// teams.addParams(params); -// Request request = teams.delete("taxonomyId").request(); -// Assertions.assertEquals(5, request.headers().names().size()); -// Assertions.assertEquals("DELETE", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(3, request.url().pathSegments().size()); -// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); -// Assertions.assertNull(request.body()); -// Assertions.assertNull(request.url().encodedQuery()); -// Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); -// } -// -// @Test -// void createTest() { -// JSONObject obj = new JSONObject(); -// obj.put("name", "sample"); -// Request request = teams.create(obj).request(); -// Assertions.assertEquals(4, request.headers().names().size()); -// Assertions.assertEquals("POST", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(2, request.url().pathSegments().size()); -// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); -// Assertions.assertNull(request.url().encodedQuery()); -// Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies", request.url().toString()); -// } -// -// @Test -// void testCreateTerm() { -// terms.clearParams(); -// JSONObject term = new JSONObject(); -// JSONObject termDetails = new JSONObject(); -// termDetails.put("uid", "term_1"); -// termDetails.put("name", "Term 1"); -// termDetails.put("description", "Term 1 Description for Taxonomy 1"); -// term.put("term", termDetails); -// term.put("parent_uid", null); -// Request request = terms.create(term).request(); -// Assertions.assertEquals(2, request.headers().names().size()); -// Assertions.assertEquals("POST", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(4, request.url().pathSegments().size()); -// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); -// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); -// Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); -// Assertions.assertEquals("terms", request.url().pathSegments().get(3)); -// Assertions.assertNotNull(request.body()); -// Assertions.assertNull(request.url().encodedQuery()); -// Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/auth999999999/terms", -// request.url().toString()); -// } -// -// @Test -// void testFindTerm() { -// terms.clearParams(); -// terms.addParam("limit", 3); -// Request request = terms.find().request(); -// Assertions.assertEquals(2, request.headers().names().size()); -// Assertions.assertEquals("GET", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(4, request.url().pathSegments().size()); -// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); -// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); -// Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); -// Assertions.assertEquals("terms", request.url().pathSegments().get(3)); -// Assertions.assertNull(request.body()); -// Assertions.assertEquals("limit=3", request.url().encodedQuery()); -// Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/" + _uid + "/terms?limit=3", -// request.url().toString()); -// } -// -// @Test -// void testFetchTerm() { -// terms.clearParams(); -// terms.addParam("include_referenced_entries_count", true); -// terms.addParam("include_children_count", false); -// Request request = terms.fetch(_uid).request(); -// Assertions.assertEquals(2, request.headers().names().size()); -// Assertions.assertEquals("GET", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(5, request.url().pathSegments().size()); -// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); -// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); -// Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); -// Assertions.assertEquals("terms", request.url().pathSegments().get(3)); -// Assertions.assertNull(request.body()); -// Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", -// request.url().encodedQuery()); -// Assertions.assertEquals( -// "https://api.contentstack.io/v3/taxonomies/auth999999999/terms/auth999999999?include_children_count=false&include_referenced_entries_count=true", -// request.url().toString()); -// } -// -// @Test -// void testTermUpdate() { -// terms.clearParams(); -// HashMap headers = new HashMap<>(); -// HashMap params = new HashMap<>(); -// terms.addParam("include_referenced_entries_count", true); -// terms.addParam("include_children_count", false); -// terms.addHeader("Accept-Encoding", "UTF-8"); -// headers.put("Accept-Encoding", "UTF-8"); -// params.put("include_children_count", "true"); -// terms.addParams(params); -// terms.addHeaders(headers); -// Request request = terms.update(_uid, new JSONObject()).request(); -// Assertions.assertEquals(3, request.headers().names().size()); -// Assertions.assertEquals("PUT", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(5, request.url().pathSegments().size()); -// } -// -// @Test -// void testTermSearch() { -// terms.clearParams(); -// Request request = terms.search("contentstack").request(); -// Assertions.assertEquals(2, request.headers().names().size()); -// Assertions.assertEquals("GET", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(4, request.url().pathSegments().size()); -// } -// -// @Test -// void testDescendantsTerm() { -// terms.clearParams(); -// terms.addParam("include_count", true); -// Request request = terms.descendants("termId45").request(); -// Assertions.assertEquals(2, request.headers().names().size()); -// Assertions.assertEquals("GET", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(6, request.url().pathSegments().size()); -// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); -// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); -// Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); -// Assertions.assertEquals("terms", request.url().pathSegments().get(3)); -// Assertions.assertNull(request.body()); -// Assertions.assertEquals("include_count=true", request.url().encodedQuery()); -// Assertions.assertEquals( -// "https://api.contentstack.io/v3/taxonomies/auth999999999/terms/termId45/descendants?include_count=true", -// request.url().toString()); -// } -// -// @Test -// void testAncestorsTerm() { -// terms.clearParams(); -// terms.addParam("include_referenced_entries_count", true); -// terms.addParam("include_children_count", false); -// Request request = terms.ancestors("termId45").request(); -// Assertions.assertEquals(3, request.headers().names().size()); -// Assertions.assertEquals("GET", request.method()); -// Assertions.assertTrue(request.url().isHttps()); -// Assertions.assertEquals("api.contentstack.io", request.url().host()); -// Assertions.assertEquals(6, request.url().pathSegments().size()); -// Assertions.assertEquals("v3", request.url().pathSegments().get(0)); -// Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); -// Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); -// Assertions.assertEquals("terms", request.url().pathSegments().get(3)); -// Assertions.assertNull(request.body()); -// Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", -// request.url().encodedQuery()); -// Assertions.assertEquals( -// "https://api.contentstack.io/v3/taxonomies/auth999999999/terms/termId45/ancestors?include_children_count=false&include_referenced_entries_count=true", -// request.url().toString()); -// } -// -// @Test -// void findTestAPI() throws IOException { -// Taxonomy taxonomy = new Contentstack.Builder() -// .setAuthtoken(TestClient.AUTHTOKEN) -// .setHost("api.contentstack.io") -// .build() -// .stack("blt12c1ba95c1b11e88", "") -// .taxonomy(); -// Response response = taxonomy.addHeader("authtoken", "blt67b95aeb964f5262").find().execute(); -// System.out.println(response); -// } - -} From ff61e282174825eb58dfc1668aeb248c0a9c4e45 Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Fri, 15 Dec 2023 13:20:39 +0530 Subject: [PATCH 012/197] CS-42937 : Support for Early Access Header CS-42936 : Taxonomy document and Test --- src/main/java/com/contentstack/cms/stack/Entry.java | 1 + src/main/java/com/contentstack/cms/stack/Stack.java | 2 +- src/main/java/com/contentstack/cms/stack/Terms.java | 12 ++++++------ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/contentstack/cms/stack/Entry.java b/src/main/java/com/contentstack/cms/stack/Entry.java index 13960a9f..41de44a8 100644 --- a/src/main/java/com/contentstack/cms/stack/Entry.java +++ b/src/main/java/com/contentstack/cms/stack/Entry.java @@ -104,6 +104,7 @@ public Entry addHeaders(@NotNull HashMap headers) { * Set header for the request * * @param key Removes query param using key of request + * @return instance of {@link Entry} */ public Entry removeParam(@NotNull String key) { this.params.remove(key); diff --git a/src/main/java/com/contentstack/cms/stack/Stack.java b/src/main/java/com/contentstack/cms/stack/Stack.java index 97b38548..fb2f36fd 100644 --- a/src/main/java/com/contentstack/cms/stack/Stack.java +++ b/src/main/java/com/contentstack/cms/stack/Stack.java @@ -845,7 +845,7 @@ public Taxonomy taxonomy() { * * @param taxonomyUid the taxonomy uid * @return instance of Taxonomy - *

    + *
    *
          * {@code
          * Stack stack = new Contentstack.Builder().setAuthtoken("authtoken").build().stack();
    diff --git a/src/main/java/com/contentstack/cms/stack/Terms.java b/src/main/java/com/contentstack/cms/stack/Terms.java
    index 9ab07985..e9e2a154 100644
    --- a/src/main/java/com/contentstack/cms/stack/Terms.java
    +++ b/src/main/java/com/contentstack/cms/stack/Terms.java
    @@ -116,7 +116,7 @@ public Terms addHeaders(@NotNull HashMap headers) {
          * Create Terms call.
          *
          * @param body The JSONObject request body
    -     * @return instance of Call 

    Example
         {@code
    +     * @return instance of Call 
    Example
         {@code
          *     Stack stack = new Contentstack.Builder().build().stack(headers);
          *     JSONObject body = new JSONObject();
          *     Term term = stack.taxonomy("taxonomyId").terms().create(body);
    @@ -161,7 +161,7 @@ public Call create(@NotNull JSONObject body) {
          * limit - Limit the result to number of documents/nodes
          * 
          * 
- *

+ *
* Example *
      *     {@code
@@ -181,7 +181,7 @@ public Call find() {
      * Fetch single term based on term uid.
      *
      * @param termUid The term for which we need the details
-     * @return instance of call 

Supported Query Parameters: to use query parameters use #addParams("key", "value");

  • include_children_count - Include count of number of children under each term
  • include_referenced_entries_count - Include count of the entries where this term is referred

Example
     {@code
+     * @return instance of call 
Supported Query Parameters: to use query parameters use #addParams("key", "value");
  • include_children_count - Include count of number of children under each term
  • include_referenced_entries_count - Include count of the entries where this term is referred

Example
     {@code
      *     Stack stack = new Contentstack.Builder().build().stack(headers);
      *     Term term = stack.taxonomy("taxonomyId").terms().find();
      *     } 
@@ -198,7 +198,7 @@ public Call fetch(@NotNull String termUid) { * @return The details of the term descendants

URL/Query parameters

  • depth - Include the terms upto the depth specified if set to a number greater than 0, include all the terms if set to 0, default depth will be set to 1
  • include_children_count - Include count of number of children under each term
  • include_referenced_entries_count - Include count of the entries where atleast 1 term of this taxonomy is referred
  • include_count - Include count of the documents/nodes that matched the query
  • skip - Skip the number of documents/nodes
  • limit - Limit the result to number of documents/nodes

Example

 {@code
      *      Stack stack = new Contentstack.Builder().build().stack(headers);
      *      Term term = stack.taxonomy("taxonomyId").terms().descendants("termId").;
-     *     }     

+ * }


*/ public Call descendants(@NotNull String termUid) { return this.taxonomyService.descendantsTerm(this.headers, this.taxonomyId, termUid, this.params); @@ -250,7 +250,7 @@ public Call update(@NotNull String termUid, @NotNull JSONObject bo * "order": 2 * } * } - *

+ *
* //Under an existing Term on a different level: * { * "term": { @@ -258,7 +258,7 @@ public Call update(@NotNull String termUid, @NotNull JSONObject bo * "order": 5 * } * } - *

+ *
* //Under an existing Term on the same level(Reorder Term): * { * "term": { From c3fe769a184bbbbea2670af163bdc0db6f72f4db Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Tue, 16 Jan 2024 11:46:53 +0530 Subject: [PATCH 013/197] CS-42937 : Support for Early Access Header CS-42936 : Taxonomy document and Test --- LICENSE | 2 +- README.md | 2 +- changelog.md | 26 ++++++++++++++++++- pom.xml | 2 +- .../java/com/contentstack/cms/core/Util.java | 6 ++--- .../com/contentstack/cms/stack/Entry.java | 23 +++++++++++++--- .../contentstack/cms/stack/EntryService.java | 7 +++++ .../com/contentstack/cms/stack/Taxonomy.java | 12 +++++++++ .../cms/stack/TaxonomyService.java | 7 +++++ .../cms/ContentstackUnitTest.java | 2 +- .../cms/stack/EntryFieldUnitTests.java | 20 ++++++++++++++ .../contentstack/cms/stack/TaxonomyTest.java | 23 ++++++++++++++++ 12 files changed, 121 insertions(+), 11 deletions(-) diff --git a/LICENSE b/LICENSE index 981ba261..d77c7f4e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2012 - 2023 Contentstack +Copyright (c) 2012 - 2024 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 diff --git a/README.md b/README.md index 2e119302..0ef4f52a 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ if(response.isSuccessful()){ ### The MIT License (MIT) -Copyright © 2012-2023 [Contentstack](https://www.contentstack.com/). All Rights Reserved +Copyright © 2012-2024 [Contentstack](https://www.contentstack.com/). All Rights Reserved Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the diff --git a/changelog.md b/changelog.md index 55ed9d34..5b25c619 100644 --- a/changelog.md +++ b/changelog.md @@ -1,11 +1,34 @@ # Changelog +## v1.2.0 + +### Dec 18, 2023 + +- New Features: + - Early access header support + + + +## v1.1.0 + +### Oct 31, 2023 + +- New Features: + - Taxonomy + - Teams + +Bug Fixes: + - KeepAliveDuration + - SNYK issues fixed + + ## v1.0.0 ### Aug 01, 2023 + - Bug Fixed For un-localize An Entry - Fixed Timeout Issue -- #32 Fixed +- #32 Fixed - Code coverage improvements - NRP support added - General improvements with minor breaking changes @@ -15,4 +38,5 @@ ### Initial Release ### Oct 20, 2022 + Initial release for Contentstack CMA base Java management SDK diff --git a/pom.xml b/pom.xml index f71bf4c1..42a88534 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.1.0 + 1.3.0 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach diff --git a/src/main/java/com/contentstack/cms/core/Util.java b/src/main/java/com/contentstack/cms/core/Util.java index 48a0fa29..8ce1a9f1 100644 --- a/src/main/java/com/contentstack/cms/core/Util.java +++ b/src/main/java/com/contentstack/cms/core/Util.java @@ -15,10 +15,10 @@ */ public class Util { - // The line `public static final String SDK_VERSION = "1.0.0";` + // The line `public static final String SDK_VERSION = "1.2.0";` // named `SDK_VERSION` of type `String`. The value of this constant is set to - // "1.0.0". - public static final String SDK_VERSION = "1.0.0"; + // "1.2.0". + public static final String SDK_VERSION = "1.2.0"; static final String PRIVATE_CONSTRUCTOR = "private constructor can't be accessed outside the class"; public static final Boolean RETRY_ON_FAILURE = true; diff --git a/src/main/java/com/contentstack/cms/stack/Entry.java b/src/main/java/com/contentstack/cms/stack/Entry.java index 41de44a8..10351804 100644 --- a/src/main/java/com/contentstack/cms/stack/Entry.java +++ b/src/main/java/com/contentstack/cms/stack/Entry.java @@ -744,12 +744,12 @@ public Call publishWithReference(@NotNull JSONObject requestBody) } /** - * To Unpublish an entry call will unpublish an entry at once, and also, gives - * you the provision to unpublish an + * To Un-publish an entry call will un-publish an entry at once, and also, gives + * you the provision to un-publish an * entry automatically at a later date/time. *

* In the 'Body' section, you can specify the locales and environments from - * which you want to unpublish the entry. + * which you want to un-publish the entry. * These details should be specified in the *

* entry parameter. However, if @@ -778,4 +778,21 @@ public Call unpublish(@NotNull JSONObject requestBody) { return this.service.unpublish(this.headers, this.contentTypeUid, this.entryUid, requestBody); } + + /** + * Get instance of taxonomy search filter class instance through which we can query on taxonomy based on content type + *

Examples

+ *

+ *

+     *           JSONObject query = new JSONObject();
+     *         query.put("taxonomies.taxonomy_uid", "{ \"$in\" : [\"term_uid1\" , \"term_uid2\" ] }");
+     *         Response response = entry.query(query).execute();
+     *     
+ * instance of {@link Terms} + */ + public Call query(@NotNull JSONObject query) { + validateCT(); + return this.service.filterTaxonomy(this.headers, this.contentTypeUid, query); + } + } diff --git a/src/main/java/com/contentstack/cms/stack/EntryService.java b/src/main/java/com/contentstack/cms/stack/EntryService.java index d5ef1e23..3fe6d220 100644 --- a/src/main/java/com/contentstack/cms/stack/EntryService.java +++ b/src/main/java/com/contentstack/cms/stack/EntryService.java @@ -146,4 +146,11 @@ Call unpublish( @Path("content_type_uid") String contentTypeUid, @Path("entry_uid") String entryUid, @Body JSONObject requestBody); + + @GET("content_types/{content_type_uid}/entries") + Call filterTaxonomy( + @HeaderMap Map headers, + @Path("content_type_uid") String contentTypeUid, + @Query("query") JSONObject queryObject); + } diff --git a/src/main/java/com/contentstack/cms/stack/Taxonomy.java b/src/main/java/com/contentstack/cms/stack/Taxonomy.java index 020270e8..fce2db5a 100644 --- a/src/main/java/com/contentstack/cms/stack/Taxonomy.java +++ b/src/main/java/com/contentstack/cms/stack/Taxonomy.java @@ -251,4 +251,16 @@ public Terms terms() { return new Terms(this.taxonomyService, this.headers, this.taxonomyId); } + + /** + * Get instance of taxonomy search filter class instance through which we can query on taxonomy based on Entry + *

Example usage:

+ *
JSONObject object = new JSonObject();
+ *
object.put("taxonomies.color", Object)
+ *
Taxonomy taxonomy = stack("authtoken").taxonomy("taxonomyId").filterTaxonomy(object);
+ */ + public Call query(JSONObject query) { + return this.taxonomyService.filterTaxonomy(this.headers, query); + } + } diff --git a/src/main/java/com/contentstack/cms/stack/TaxonomyService.java b/src/main/java/com/contentstack/cms/stack/TaxonomyService.java index f104c368..571a9bcb 100644 --- a/src/main/java/com/contentstack/cms/stack/TaxonomyService.java +++ b/src/main/java/com/contentstack/cms/stack/TaxonomyService.java @@ -91,4 +91,11 @@ Call reorder( Call searchTerm( @HeaderMap HashMap headers, @Query("typeahead") String termString); + + + @GET("taxonomies/entries") + Call filterTaxonomy( + @HeaderMap HashMap headers, + @Query("query") JSONObject queryObject + ); } diff --git a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java index ea4e09f6..eaca866e 100644 --- a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java +++ b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java @@ -250,7 +250,7 @@ public void testValidOrganization() { @Test public void testNullOrganizationUid() { Contentstack client = new Contentstack.Builder().setAuthtoken("fake@authtoken").build(); - Assertions.assertThrows(IllegalArgumentException.class, () -> client.organization(null)); + Assertions.assertThrows(NullPointerException.class, () -> client.organization(null)); } @Test diff --git a/src/test/java/com/contentstack/cms/stack/EntryFieldUnitTests.java b/src/test/java/com/contentstack/cms/stack/EntryFieldUnitTests.java index f43cbc0a..f9701a5a 100644 --- a/src/test/java/com/contentstack/cms/stack/EntryFieldUnitTests.java +++ b/src/test/java/com/contentstack/cms/stack/EntryFieldUnitTests.java @@ -3,6 +3,7 @@ import com.contentstack.cms.Contentstack; import com.contentstack.cms.TestClient; import okhttp3.Request; +import okhttp3.ResponseBody; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.junit.jupiter.api.*; @@ -768,4 +769,23 @@ void testHeader() { Assertions.assertEquals(3, resp.headers().size()); } + @Test + void queryFiltersOnTaxonomy() { + JSONObject query = new JSONObject(); + query.put("taxonomies.taxonomy_uid", "{ \"$in\" : [\"term_uid1\" , \"term_uid2\" ] }"); + Request request = entryInstance.query(query).request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); + Assertions.assertEquals("product", request.url().pathSegments().get(2)); + Assertions.assertEquals("entries", request.url().pathSegments().get(3)); + Assertions.assertNull(request.body()); + Assertions.assertEquals("query={\"taxonomies.taxonomy_uid\":\"{ \\\"$in\\\" : [\\\"term_uid1\\\" , \\\"term_uid2\\\" ] }\"}", request.url().query()); + + } + } diff --git a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java index d65d7c3b..23d9fca7 100644 --- a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java +++ b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java @@ -365,4 +365,27 @@ void findTestAPI() throws IOException { System.out.println(response); } + @Test + void queryFiltersOnTaxonomy() { + Taxonomy taxonomy = new Contentstack.Builder() + .setAuthtoken(TestClient.AUTHTOKEN) + .setHost("api.contentstack.io") + .build() + .stack("blt12c1ba95c1b11e88") + .taxonomy(); + JSONObject query = new JSONObject(); + query.put("taxonomies.taxonomy_uid", "{ \"$in\" : [\"term_uid1\" , \"term_uid2\" ] }"); + Request request = taxonomy.query(query).request(); + Assertions.assertEquals(1, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals("entries", request.url().pathSegments().get(2)); + Assertions.assertNull(request.body()); + Assertions.assertEquals("query={\"taxonomies.taxonomy_uid\":\"{ \\\"$in\\\" : [\\\"term_uid1\\\" , \\\"term_uid2\\\" ] }\"}", request.url().query()); + } + } From 8f0885aa569abc2aa4a8dd9d50a4b08613689320 Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Tue, 16 Jan 2024 12:00:52 +0530 Subject: [PATCH 014/197] CS-42937 : Support for Early Access Header CS-42936 : Taxonomy document and Test --- src/main/java/com/contentstack/cms/stack/Entry.java | 7 ++++--- src/main/java/com/contentstack/cms/stack/Locale.java | 2 -- .../java/com/contentstack/cms/stack/ManagementToken.java | 1 - src/main/java/com/contentstack/cms/stack/Taxonomy.java | 3 +++ src/main/java/com/contentstack/cms/stack/Tokens.java | 2 -- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/contentstack/cms/stack/Entry.java b/src/main/java/com/contentstack/cms/stack/Entry.java index 10351804..6fb633af 100644 --- a/src/main/java/com/contentstack/cms/stack/Entry.java +++ b/src/main/java/com/contentstack/cms/stack/Entry.java @@ -782,13 +782,14 @@ public Call unpublish(@NotNull JSONObject requestBody) { /** * Get instance of taxonomy search filter class instance through which we can query on taxonomy based on content type *

Examples

- *

*

      *           JSONObject query = new JSONObject();
      *         query.put("taxonomies.taxonomy_uid", "{ \"$in\" : [\"term_uid1\" , \"term_uid2\" ] }");
-     *         Response response = entry.query(query).execute();
+     *         Call response = entry.query(query).execute();
      *     
- * instance of {@link Terms} + * + * @param query the request body of type {@link JSONObject} + * @return instance of {@link Terms} */ public Call query(@NotNull JSONObject query) { validateCT(); diff --git a/src/main/java/com/contentstack/cms/stack/Locale.java b/src/main/java/com/contentstack/cms/stack/Locale.java index 24662b86..fb7a1f49 100644 --- a/src/main/java/com/contentstack/cms/stack/Locale.java +++ b/src/main/java/com/contentstack/cms/stack/Locale.java @@ -11,8 +11,6 @@ import java.util.Map; /** - * Languages - *
* Contentstack has a sophisticated multilingual capability. It allows you to * create and publish entries in any * language. This feature allows you to set up multilingual websites and cater diff --git a/src/main/java/com/contentstack/cms/stack/ManagementToken.java b/src/main/java/com/contentstack/cms/stack/ManagementToken.java index 95d3334d..cd7a8923 100644 --- a/src/main/java/com/contentstack/cms/stack/ManagementToken.java +++ b/src/main/java/com/contentstack/cms/stack/ManagementToken.java @@ -9,7 +9,6 @@ import java.util.HashMap; /** - * Management tokens:
* To authenticate Content Management API (CMA) requests over your stack * content, you can use Management Tokens *
diff --git a/src/main/java/com/contentstack/cms/stack/Taxonomy.java b/src/main/java/com/contentstack/cms/stack/Taxonomy.java index fce2db5a..ea7ee77a 100644 --- a/src/main/java/com/contentstack/cms/stack/Taxonomy.java +++ b/src/main/java/com/contentstack/cms/stack/Taxonomy.java @@ -258,6 +258,9 @@ public Terms terms() { *
JSONObject object = new JSonObject();
*
object.put("taxonomies.color", Object)
*
Taxonomy taxonomy = stack("authtoken").taxonomy("taxonomyId").filterTaxonomy(object);
+ * + * @param query the query of type @{@link JSONObject} + * @return instance of {@link Call} */ public Call query(JSONObject query) { return this.taxonomyService.filterTaxonomy(this.headers, query); diff --git a/src/main/java/com/contentstack/cms/stack/Tokens.java b/src/main/java/com/contentstack/cms/stack/Tokens.java index 91d0f453..11f23a50 100644 --- a/src/main/java/com/contentstack/cms/stack/Tokens.java +++ b/src/main/java/com/contentstack/cms/stack/Tokens.java @@ -4,8 +4,6 @@ import retrofit2.Retrofit; /** - * Tokens - *
* Contentstack provides different types of tokens to authorize API requests * * @author ***REMOVED*** From 5bc51c457cbbdc6c4505ca6b2edd6fe7f0439ae9 Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Tue, 16 Jan 2024 12:18:24 +0530 Subject: [PATCH 015/197] CS-42937 : Support for Early Access Header CS-42936 : Taxonomy document and Test --- changelog.md | 19 +++++++++++++------ .../java/com/contentstack/cms/core/Util.java | 4 ++-- .../contentstack/cms/stack/TaxonomyTest.java | 4 +--- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/changelog.md b/changelog.md index 5b25c619..1f71bc61 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## v1.3.0 + +### Jan 18, 2024 + +- New Features: + - Query filter support in Taxonomy + - query filter support in Entry + - query filter support in Taxonomy + ## v1.2.0 ### Dec 18, 2023 @@ -7,20 +16,18 @@ - New Features: - Early access header support - - ## v1.1.0 ### Oct 31, 2023 - New Features: - - Taxonomy - - Teams + - Taxonomy + - Teams Bug Fixes: - - KeepAliveDuration - - SNYK issues fixed +- KeepAliveDuration +- SNYK issues fixed ## v1.0.0 diff --git a/src/main/java/com/contentstack/cms/core/Util.java b/src/main/java/com/contentstack/cms/core/Util.java index 8ce1a9f1..6f969dbe 100644 --- a/src/main/java/com/contentstack/cms/core/Util.java +++ b/src/main/java/com/contentstack/cms/core/Util.java @@ -15,10 +15,10 @@ */ public class Util { - // The line `public static final String SDK_VERSION = "1.2.0";` + // The line `public static final String SDK_VERSION = "1.3.0";` // named `SDK_VERSION` of type `String`. The value of this constant is set to // "1.2.0". - public static final String SDK_VERSION = "1.2.0"; + public static final String SDK_VERSION = "1.3.0"; static final String PRIVATE_CONSTRUCTOR = "private constructor can't be accessed outside the class"; public static final Boolean RETRY_ON_FAILURE = true; diff --git a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java index 23d9fca7..13d10a10 100644 --- a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java +++ b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java @@ -25,8 +25,6 @@ class TaxonomyTest { protected static Terms terms; protected static JSONObject body; - // Create a JSONObject, JSONObject could be created in multiple ways. - // We choose JSONParser that converts string to JSONObject static String theBody = "{\n" + " \"taxonomy\": {\n" + " \"name\": \"Taxonomy 1\",\n" + @@ -371,7 +369,7 @@ void queryFiltersOnTaxonomy() { .setAuthtoken(TestClient.AUTHTOKEN) .setHost("api.contentstack.io") .build() - .stack("blt12c1ba95c1b11e88") + .stack("fakestackkey") .taxonomy(); JSONObject query = new JSONObject(); query.put("taxonomies.taxonomy_uid", "{ \"$in\" : [\"term_uid1\" , \"term_uid2\" ] }"); From dd93b60b260213b5920d6a7405f3680dd626ca11 Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Mon, 19 Feb 2024 19:27:42 +0530 Subject: [PATCH 016/197] Auto-detect content type while uploading asset --- pom.xml | 8 +-- .../com/contentstack/cms/stack/Asset.java | 40 +----------- .../contentstack/cms/stack/FileUploader.java | 63 +++++++++++++++++++ .../com/contentstack/cms/stack/Taxonomy.java | 2 +- .../contentstack/cms/stack/AssetAPITest.java | 7 +-- .../cms/stack/EntryFieldsAPITest.java | 22 +++++++ 6 files changed, 96 insertions(+), 46 deletions(-) create mode 100644 src/main/java/com/contentstack/cms/stack/FileUploader.java diff --git a/pom.xml b/pom.xml index 42a88534..73cfb945 100644 --- a/pom.xml +++ b/pom.xml @@ -91,10 +91,10 @@ 3.1.8 2.9.0 2.9.0 - 5.0.0-alpha.11 + 5.0.0-alpha.12 0.8.7 1.18.30 - 5.10.0 + 5.10.1 5.8.0-M1 2.10.1 3.3 @@ -138,7 +138,7 @@ org.jetbrains annotations - 24.0.1 + 24.1.0 provided @@ -168,7 +168,7 @@ org.mockito mockito-core - 5.6.0 + 5.9.0 test diff --git a/src/main/java/com/contentstack/cms/stack/Asset.java b/src/main/java/com/contentstack/cms/stack/Asset.java index aa3e531e..c0b93435 100644 --- a/src/main/java/com/contentstack/cms/stack/Asset.java +++ b/src/main/java/com/contentstack/cms/stack/Asset.java @@ -327,50 +327,16 @@ public Call uploadAsset(@NotNull String filePath, String descripti * @return Call */ public Call uploadAsset(@NotNull String filePath, String parentUid, String title, String description, String[] tags) { - RequestBody body = RequestBody.create(MediaType.parse("multipart/form-data"), description); + RequestBody body = RequestBody.create(Objects.requireNonNull(MediaType.parse("multipart/form-data")), description); MultipartBody.Part partFile = createMultipartBody(filePath, parentUid, title, description, tags); return this.service.uploadAsset(this.headers, partFile, body, this.params); } - private String tagConvertor(String[] tags) { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < tags.length; i++) { - stringBuilder.append(tags[i]); - if (i < tags.length - 1) { - stringBuilder.append(", "); - } - } - return stringBuilder.toString(); - } - private MultipartBody.Part createMultipartBody(String filePath, String parentUid, String title, String description, String[] tags) { MultipartBody.Builder builder = new MultipartBody.Builder(); builder.setType(MultipartBody.FORM); - - if (!filePath.isEmpty()) { - File file = new File(filePath); - if (file.exists()) { - RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file); - builder.addFormDataPart("asset[upload]", file.getName(), fileBody); - } - } - - // Add other parts - if (parentUid != null) { - builder.addFormDataPart("asset[parent_uid]", parentUid); - } - if (title != null) { - builder.addFormDataPart("asset[title]", title); - } - if (description != null) { - builder.addFormDataPart("asset[description]", description); - } - if (tags != null) { - builder.addFormDataPart("asset[tags]", tagConvertor(tags)); - } - - return builder.build().part(0); + return new FileUploader().createMultipartBody(filePath, parentUid, title, description, tags); } @@ -403,7 +369,7 @@ private MultipartBody.Part createMultipartBody(String filePath, String parentUid public Call replace(@NotNull String filePath, @NotNull String description) { Objects.requireNonNull(this.assetUid, "Asset Uid Can Not Be Null OR Empty"); MultipartBody.Part assetPath = uploadFile(filePath); - RequestBody body = RequestBody.create(MediaType.parse(String.valueOf(MultipartBody.FORM)), description); + RequestBody body = RequestBody.create(Objects.requireNonNull(MediaType.parse(String.valueOf(MultipartBody.FORM))), description); return this.service.replace(this.headers, this.assetUid, assetPath, body, this.params); } diff --git a/src/main/java/com/contentstack/cms/stack/FileUploader.java b/src/main/java/com/contentstack/cms/stack/FileUploader.java new file mode 100644 index 00000000..df0f6d16 --- /dev/null +++ b/src/main/java/com/contentstack/cms/stack/FileUploader.java @@ -0,0 +1,63 @@ +package com.contentstack.cms.stack; + +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Objects; + +public class FileUploader { + + + public MultipartBody.Part createMultipartBody(String filePath, String parentUid, String title, String description, String[] tags) { + MultipartBody.Builder builder = new MultipartBody.Builder(); + builder.setType(MultipartBody.FORM); + + // Check if filePath is not empty and file exists + if (!filePath.isEmpty()) { + File file = new File(filePath); + if (file.exists()) { + String contentType = getContentType(file); + RequestBody fileBody = RequestBody.create(Objects.requireNonNull(MediaType.parse(contentType)), file); + builder.addFormDataPart("asset[upload]", file.getName(), fileBody); + } + } + + // Add other parts if not null + addFormDataPartIfNotNull(builder, "asset[parent_uid]", parentUid); + addFormDataPartIfNotNull(builder, "asset[title]", title); + addFormDataPartIfNotNull(builder, "asset[description]", description); + + // Handle tags array null case + if (tags != null) { + addFormDataPartIfNotNull(builder, "asset[tags]", tagConvertor(tags)); + } + + return builder.build().part(0); + } + + // Helper method to add form data part if value is not null + private void addFormDataPartIfNotNull(MultipartBody.Builder builder, String name, String value) { + if (value != null) { + builder.addFormDataPart(name, value); + } + } + + // Helper method to get content type of file + private String getContentType(File file) { + try { + return Files.probeContentType(file.toPath()); + } catch (IOException e) { + throw new RuntimeException("Failed to determine content type of file", e); + } + } + + // Dummy implementation of tagConvertor method + private String tagConvertor(String[] tags) { + // Implement your tag conversion logic here + return String.join(",", tags); + } +} diff --git a/src/main/java/com/contentstack/cms/stack/Taxonomy.java b/src/main/java/com/contentstack/cms/stack/Taxonomy.java index ea7ee77a..50640831 100644 --- a/src/main/java/com/contentstack/cms/stack/Taxonomy.java +++ b/src/main/java/com/contentstack/cms/stack/Taxonomy.java @@ -257,7 +257,7 @@ public Terms terms() { *

Example usage:

*
JSONObject object = new JSonObject();
*
object.put("taxonomies.color", Object)
- *
Taxonomy taxonomy = stack("authtoken").taxonomy("taxonomyId").filterTaxonomy(object);
+ *
Taxonomy taxonomy = stack("authtoken").taxonomy("taxonomyId").query(object);
* * @param query the query of type @{@link JSONObject} * @return instance of {@link Call} diff --git a/src/test/java/com/contentstack/cms/stack/AssetAPITest.java b/src/test/java/com/contentstack/cms/stack/AssetAPITest.java index 05b95d4d..6c38a297 100644 --- a/src/test/java/com/contentstack/cms/stack/AssetAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/AssetAPITest.java @@ -30,7 +30,7 @@ static void setup() { @Order(1) @Test - void testFindAssets() throws IOException { + void testFindAssets() { asset.clearParams(); asset.addParam("include_folders", true); asset.addParam("environment", "production"); @@ -141,10 +141,9 @@ void testAssetUpload() { asset.addHeader("api_key", API_KEY); asset.addHeader("authorization", MANAGEMENT_TOKEN); asset.addHeader("authtoken", AUTHTOKEN); - String filePath = "/Users/shaileshmishra/Desktop/pexels.png"; - String description = "The calender has been placed to assets by ***REMOVED***"; + String filePath = "/Users/shaileshmishra/Desktop/image.jpeg"; + String description = "The calender has been placed to assets by shaileshmishra"; Request request = asset.uploadAsset(filePath, description).request(); - // The assertions Assertions.assertEquals(3, request.headers().size()); Assertions.assertTrue(request.headers().names().contains("api_key")); diff --git a/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java b/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java index e8de25a4..f35414e2 100644 --- a/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java @@ -2,8 +2,10 @@ import com.contentstack.cms.TestClient; import okhttp3.Request; +import okhttp3.ResponseBody; import org.json.simple.JSONObject; import org.junit.jupiter.api.*; +import retrofit2.Response; import java.io.IOException; @@ -363,4 +365,24 @@ void testPublishWithReference() { request.url().toString()); } + + @Test + public void testEntryQuery() { + JSONObject query = new JSONObject(); + query.put("taxonomies.taxonomy_uid", "{ \"$in\" : [\"term_uid1\" , \"term_uid2\" ] }"); + Request request = entry.query(query).request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); + Assertions.assertEquals("product", request.url().pathSegments().get(2)); + Assertions.assertEquals("entries", request.url().pathSegments().get(3)); + Assertions.assertNull(request.body()); + Assertions.assertEquals("query={\"taxonomies.taxonomy_uid\":\"{ \\\"$in\\\" : [\\\"term_uid1\\\" , \\\"term_uid2\\\" ] }\"}", request.url().query()); + + } + } From 15e2cfceaf55980154d6ffb14d5d008bcbb3d5d7 Mon Sep 17 00:00:00 2001 From: Shailesh Mishra Date: Mon, 19 Feb 2024 19:28:24 +0530 Subject: [PATCH 017/197] Auto-detect content type while uploading asset --- pom.xml | 2 +- src/main/java/com/contentstack/cms/core/Util.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 73cfb945..17bbc160 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.3.0 + 1.3.1 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach diff --git a/src/main/java/com/contentstack/cms/core/Util.java b/src/main/java/com/contentstack/cms/core/Util.java index 6f969dbe..7049f198 100644 --- a/src/main/java/com/contentstack/cms/core/Util.java +++ b/src/main/java/com/contentstack/cms/core/Util.java @@ -18,7 +18,7 @@ public class Util { // The line `public static final String SDK_VERSION = "1.3.0";` // named `SDK_VERSION` of type `String`. The value of this constant is set to // "1.2.0". - public static final String SDK_VERSION = "1.3.0"; + public static final String SDK_VERSION = "1.3.1"; static final String PRIVATE_CONSTRUCTOR = "private constructor can't be accessed outside the class"; public static final Boolean RETRY_ON_FAILURE = true; From 9115bd5b128765f6998d1a671ebf02ee3763cbbe Mon Sep 17 00:00:00 2001 From: harshithad0703 Date: Wed, 21 Feb 2024 15:57:21 +0530 Subject: [PATCH 018/197] chore: :memo: update changeLog --- changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/changelog.md b/changelog.md index 1f71bc61..b21da333 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## v1.3.1 + +### Feb 22, 2024 + +- Fix: + - auto checking data type of the asset object + ## v1.3.0 ### Jan 18, 2024 From 17e5fdeadf8ea0f7e1862e3923032f652e8cc7a8 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 12 Mar 2024 18:02:54 +0530 Subject: [PATCH 019/197] passed headers --- .../com/contentstack/cms/stack/Alias.java | 6 +- .../com/contentstack/cms/stack/Asset.java | 4 +- .../com/contentstack/cms/stack/AuditLog.java | 7 ++- .../com/contentstack/cms/stack/Branch.java | 6 +- .../contentstack/cms/stack/BulkOperation.java | 4 +- .../contentstack/cms/stack/DeliveryToken.java | 7 ++- .../contentstack/cms/stack/Environment.java | 6 +- .../contentstack/cms/stack/Extensions.java | 6 +- .../com/contentstack/cms/stack/Folder.java | 6 +- .../contentstack/cms/stack/GlobalField.java | 7 ++- .../com/contentstack/cms/stack/Label.java | 7 ++- .../com/contentstack/cms/stack/Locale.java | 6 +- .../cms/stack/ManagementToken.java | 7 ++- .../contentstack/cms/stack/PublishQueue.java | 7 ++- .../com/contentstack/cms/stack/Release.java | 9 ++- .../contentstack/cms/stack/ReleaseItem.java | 4 +- .../com/contentstack/cms/stack/Roles.java | 7 ++- .../com/contentstack/cms/stack/Stack.java | 56 +++++++++---------- .../com/contentstack/cms/stack/Taxonomy.java | 2 + .../com/contentstack/cms/stack/Tokens.java | 14 +++-- .../com/contentstack/cms/stack/Webhook.java | 7 ++- .../com/contentstack/cms/stack/Workflow.java | 7 ++- 22 files changed, 122 insertions(+), 70 deletions(-) diff --git a/src/main/java/com/contentstack/cms/stack/Alias.java b/src/main/java/com/contentstack/cms/stack/Alias.java index 906f32ad..c074fc1d 100644 --- a/src/main/java/com/contentstack/cms/stack/Alias.java +++ b/src/main/java/com/contentstack/cms/stack/Alias.java @@ -35,8 +35,9 @@ public class Alias implements BaseImplementation { // also creates an // instance of the `AliasService` interface using the provided `Retrofit` // instance. - protected Alias(Retrofit instance) { + protected Alias(Retrofit instance,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.headers.put("Content-Type", "application/json"); params = new HashMap<>(); this.service = instance.create(AliasService.class); @@ -45,8 +46,9 @@ protected Alias(Retrofit instance) { // The `protected Alias(Retrofit instance, String aliasUid)` constructor is used // to create an // instance of the `Alias` class with a specific alias UID. - protected Alias(Retrofit instance, String aliasUid) { + protected Alias(Retrofit instance,Map headers, String aliasUid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.headers.put("Content-Type", "application/json"); params = new HashMap<>(); this.uid = aliasUid; diff --git a/src/main/java/com/contentstack/cms/stack/Asset.java b/src/main/java/com/contentstack/cms/stack/Asset.java index c0b93435..c78abb87 100644 --- a/src/main/java/com/contentstack/cms/stack/Asset.java +++ b/src/main/java/com/contentstack/cms/stack/Asset.java @@ -117,7 +117,7 @@ protected void clearParams() { * @return Folder */ public Folder folder() { - return new Folder(this.instance); + return new Folder(this.instance,this.headers); } /** @@ -127,7 +127,7 @@ public Folder folder() { * @return Folder */ public Folder folder(@NotNull String folderUid) { - return new Folder(this.instance, folderUid); + return new Folder(this.instance,this.headers, folderUid); } /** diff --git a/src/main/java/com/contentstack/cms/stack/AuditLog.java b/src/main/java/com/contentstack/cms/stack/AuditLog.java index eac83421..d7ce42d3 100644 --- a/src/main/java/com/contentstack/cms/stack/AuditLog.java +++ b/src/main/java/com/contentstack/cms/stack/AuditLog.java @@ -7,6 +7,7 @@ import retrofit2.Retrofit; import java.util.HashMap; +import java.util.Map; import java.util.Objects; /** @@ -37,14 +38,16 @@ public class AuditLog implements BaseImplementation { protected HashMap params; private String logItemUid; - protected AuditLog(Retrofit retrofit) { + protected AuditLog(Retrofit retrofit,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.service = retrofit.create(AuditLogService.class); } - protected AuditLog(Retrofit retrofit, String uid) { + protected AuditLog(Retrofit retrofit,Map headers, String uid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.logItemUid = uid; this.service = retrofit.create(AuditLogService.class); diff --git a/src/main/java/com/contentstack/cms/stack/Branch.java b/src/main/java/com/contentstack/cms/stack/Branch.java index 68254d96..f4572d57 100644 --- a/src/main/java/com/contentstack/cms/stack/Branch.java +++ b/src/main/java/com/contentstack/cms/stack/Branch.java @@ -34,15 +34,17 @@ public class Branch implements BaseImplementation { private Retrofit instance; private String baseBranchId; - protected Branch(Retrofit instance) { + protected Branch(Retrofit instance,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.headers.put("Content-Type", "application/json"); this.params = new HashMap<>(); this.service = instance.create(BranchService.class); } - protected Branch(Retrofit instance, String uid) { + protected Branch(Retrofit instance,Map headers, String uid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.headers.put("Content-Type", "application/json"); this.baseBranchId = uid; this.params = new HashMap<>(); diff --git a/src/main/java/com/contentstack/cms/stack/BulkOperation.java b/src/main/java/com/contentstack/cms/stack/BulkOperation.java index 2e49ed32..52bcb1d8 100644 --- a/src/main/java/com/contentstack/cms/stack/BulkOperation.java +++ b/src/main/java/com/contentstack/cms/stack/BulkOperation.java @@ -8,6 +8,7 @@ import retrofit2.Retrofit; import java.util.HashMap; +import java.util.Map; /** * You can perform bulk operations such as Publish, Unpublished, and Delete on @@ -48,8 +49,9 @@ public class BulkOperation implements BaseImplementation { * * @param retrofit the retrofit */ - protected BulkOperation(Retrofit retrofit) { + protected BulkOperation(Retrofit retrofit,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.retrofit = retrofit; this.service = this.retrofit.create(BulkOperationService.class); diff --git a/src/main/java/com/contentstack/cms/stack/DeliveryToken.java b/src/main/java/com/contentstack/cms/stack/DeliveryToken.java index fe470d5d..f0aa8cc5 100644 --- a/src/main/java/com/contentstack/cms/stack/DeliveryToken.java +++ b/src/main/java/com/contentstack/cms/stack/DeliveryToken.java @@ -7,6 +7,7 @@ import retrofit2.Call; import java.util.HashMap; +import java.util.Map; import java.util.Objects; /** @@ -31,14 +32,16 @@ public class DeliveryToken implements BaseImplementation { private String tokenUid; String ERROR = "Token UID Can Not Be Null OR Empty"; - protected DeliveryToken(TokenService service) { + protected DeliveryToken(TokenService service, Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.service = service; } - protected DeliveryToken(TokenService service, @NotNull String tokenUid) { + protected DeliveryToken(TokenService service, Map headers, @NotNull String tokenUid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.tokenUid = tokenUid; this.service = service; diff --git a/src/main/java/com/contentstack/cms/stack/Environment.java b/src/main/java/com/contentstack/cms/stack/Environment.java index 29b39a33..8ef0c9d2 100644 --- a/src/main/java/com/contentstack/cms/stack/Environment.java +++ b/src/main/java/com/contentstack/cms/stack/Environment.java @@ -27,15 +27,17 @@ public class Environment implements BaseImplementation { protected final EnvironmentService service; protected String environment; - protected Environment(Retrofit instance) { + protected Environment(Retrofit instance, Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.service = instance.create(EnvironmentService.class); } - protected Environment(Retrofit instance, String environment) { + protected Environment(Retrofit instance, Map headers, String environment) { this.environment = environment; this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.service = instance.create(EnvironmentService.class); } diff --git a/src/main/java/com/contentstack/cms/stack/Extensions.java b/src/main/java/com/contentstack/cms/stack/Extensions.java index 8ccb3f57..437c8cab 100644 --- a/src/main/java/com/contentstack/cms/stack/Extensions.java +++ b/src/main/java/com/contentstack/cms/stack/Extensions.java @@ -36,14 +36,16 @@ public class Extensions implements BaseImplementation { protected HashMap params; protected String customFieldUid; - protected Extensions(Retrofit retrofit) { + protected Extensions(Retrofit retrofit,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.service = retrofit.create(ExtensionsService.class); } - protected Extensions(Retrofit retrofit, String fieldUid) { + protected Extensions(Retrofit retrofit,Map headers, String fieldUid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.customFieldUid = fieldUid; this.service = retrofit.create(ExtensionsService.class); diff --git a/src/main/java/com/contentstack/cms/stack/Folder.java b/src/main/java/com/contentstack/cms/stack/Folder.java index b8211427..a249b598 100644 --- a/src/main/java/com/contentstack/cms/stack/Folder.java +++ b/src/main/java/com/contentstack/cms/stack/Folder.java @@ -18,14 +18,16 @@ public class Folder implements BaseImplementation { protected final AssetService service; private String folderUid; - protected Folder(Retrofit instance) { + protected Folder(Retrofit instance,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); params = new HashMap<>(); this.service = instance.create(AssetService.class); } - protected Folder(Retrofit instance, String folderUid) { + protected Folder(Retrofit instance,Map headers, String folderUid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); params = new HashMap<>(); this.folderUid = folderUid; this.service = instance.create(AssetService.class); diff --git a/src/main/java/com/contentstack/cms/stack/GlobalField.java b/src/main/java/com/contentstack/cms/stack/GlobalField.java index ae78877d..47ad9599 100644 --- a/src/main/java/com/contentstack/cms/stack/GlobalField.java +++ b/src/main/java/com/contentstack/cms/stack/GlobalField.java @@ -8,6 +8,7 @@ import retrofit2.Retrofit; import java.util.HashMap; +import java.util.Map; /** * A Global field is a reusable field (or group of fields) that you can define @@ -37,14 +38,16 @@ public class GlobalField implements BaseImplementation { protected HashMap params; protected String globalFiledUid; - protected GlobalField(Retrofit retrofit) { + protected GlobalField(Retrofit retrofit,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.service = retrofit.create(GlobalFieldService.class); } - protected GlobalField(Retrofit retrofit, String uid) { + protected GlobalField(Retrofit retrofit,Map headers, String uid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.globalFiledUid = uid; this.service = retrofit.create(GlobalFieldService.class); diff --git a/src/main/java/com/contentstack/cms/stack/Label.java b/src/main/java/com/contentstack/cms/stack/Label.java index 364e9ae9..d6b3943c 100644 --- a/src/main/java/com/contentstack/cms/stack/Label.java +++ b/src/main/java/com/contentstack/cms/stack/Label.java @@ -8,6 +8,7 @@ import retrofit2.Retrofit; import java.util.HashMap; +import java.util.Map; /** * Labels allow you to group a collection of content within a stack. Using @@ -32,15 +33,17 @@ public class Label implements BaseImplementation
- + + javax.activation + activation + 1.1.1 + From 0bb630358894e4c7e07063ea338a5b809211979d Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Wed, 27 Mar 2024 18:23:41 +0530 Subject: [PATCH 025/197] fix: removed the staging documentation links --- src/main/java/com/contentstack/cms/stack/Merge.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/contentstack/cms/stack/Merge.java b/src/main/java/com/contentstack/cms/stack/Merge.java index e95f7272..7632340c 100644 --- a/src/main/java/com/contentstack/cms/stack/Merge.java +++ b/src/main/java/com/contentstack/cms/stack/Merge.java @@ -41,7 +41,7 @@ * @author ***REMOVED*** * @version v1.0.0 * @see Merge + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#merge-branches">Merge * Branches * @since 2023 -June-30 */ @@ -128,7 +128,7 @@ public Call branch(@NotNull String compareBranch, JSONObject reque * * @return the call * @see Get + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-all-merge-jobs">Get * all * Merge Jobs */ @@ -144,7 +144,7 @@ public Call find() { * @param mergeJobUid the key of the header to be added * @return the call * @see Get + * "https://www.contentstack.com/docs/developers/apis/content-management-api/#get-single-merge-job">Get * a * Single Merge Job */ From 60ff3bf86e3e32188610239dd67a1f8ff83692ec Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Thu, 28 Mar 2024 11:29:53 +0530 Subject: [PATCH 026/197] added: taxonomyAPI testcases along with mocktaxonomy folder --- .../cms/stack/TaxonomyAPITest.java | 300 ++++++++++++++++++ src/test/resources/mocktaxonomy/create.json | 8 + .../resources/mocktaxonomy/createTerm.json | 8 + src/test/resources/mocktaxonomy/update.json | 6 + .../resources/mocktaxonomy/updateTerm.json | 5 + 5 files changed, 327 insertions(+) create mode 100644 src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java create mode 100644 src/test/resources/mocktaxonomy/create.json create mode 100644 src/test/resources/mocktaxonomy/createTerm.json create mode 100644 src/test/resources/mocktaxonomy/update.json create mode 100644 src/test/resources/mocktaxonomy/updateTerm.json diff --git a/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java b/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java new file mode 100644 index 00000000..197f1083 --- /dev/null +++ b/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java @@ -0,0 +1,300 @@ +package com.contentstack.cms.stack; + +import java.io.IOException; +import java.util.HashMap; + +import org.json.simple.JSONObject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.*; +import com.contentstack.cms.Contentstack; +import com.contentstack.cms.TestClient; +import com.contentstack.cms.Utils; +import com.contentstack.cms.core.Util; + +import okhttp3.Request; +import okhttp3.ResponseBody; +import retrofit2.Response; + + + + +@Tag("api") +public class TaxonomyAPITest { + protected static String API_KEY = TestClient.API_KEY; + protected static String _uid = "sample_one"; + protected static String MANAGEMENT_TOKEN = TestClient.MANAGEMENT_TOKEN; + protected static Taxonomy taxonomy; + protected static Terms terms; + + @BeforeAll + static void setup() { + final String AUTHTOKEN = TestClient.AUTHTOKEN; + HashMap headers = new HashMap<>(); + headers.put(Util.API_KEY, API_KEY); + headers.put(Util.AUTHORIZATION, MANAGEMENT_TOKEN); + Stack stack = new Contentstack.Builder().setAuthtoken(AUTHTOKEN).build().stack(headers); + taxonomy = stack.taxonomy(); + terms = stack.taxonomy(_uid).terms(); + } + + @Test + void fetchAll(){ + + try { + Response response = taxonomy.find().execute(); + System.out.println(response.body().string()); + if (response.isSuccessful()) { + Assertions.assertTrue(response.isSuccessful()); + } else { + Assertions.assertFalse(response.isSuccessful()); + } + } catch (IOException e) { + System.out.println("Exception: " + e.getLocalizedMessage()); + } + } + + @Test + void fetchSingle() { + try { + Response response = taxonomy.fetch("regions").execute(); + System.out.println(response); + if (response.isSuccessful()) { + Assertions.assertTrue(response.isSuccessful()); + } else { + Assertions.assertFalse(response.isSuccessful()); + } + } catch (IOException e) { + System.out.println("Exception: " + e.getLocalizedMessage()); + } + } + + @Test + void createTaxonomy() throws IOException { + // add create.json + JSONObject requestBody = Utils.readJson("mocktaxonomy/create.json"); + taxonomy.addHeader(Util.API_KEY, API_KEY); + taxonomy.addHeader(Util.AUTHORIZATION, MANAGEMENT_TOKEN); + Request request = taxonomy.create(requestBody).request(); + Response resp = taxonomy.create(requestBody).execute(); + System.out.println(resp); + System.out.println(request); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("POST", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertNull(request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies", request.url().toString()); + + } + + @Test + void updateTaxonomy() { + JSONObject updateBody = Utils.readJson("mocktaxonomy/update.json"); + taxonomy.addHeader(Util.API_KEY, API_KEY); + taxonomy.addHeader(Util.AUTHORIZATION, MANAGEMENT_TOKEN); + Request request = taxonomy.update("sample_one",updateBody).request(); + System.out.println(request); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("PUT", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertNotNull(request.body()); + Assertions.assertNull(request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/sample_one", request.url().toString()); + + } + + @Test + void deleteTaxonomy(){ + taxonomy.clearParams(); + taxonomy.addHeader("Content-Type", "application/json"); + HashMap headers = new HashMap<>(); + HashMap params = new HashMap<>(); + headers.put("key_param1", "key_param_value"); + headers.put("key_param2", "key_param_value"); + params.put("key_param3", "key_param_value"); + params.put("key_param4", "key_param_value"); + taxonomy.addHeaders(headers); + taxonomy.addParams(params); + Request request = taxonomy.delete("sample_one").request(); + System.out.println(request); + Assertions.assertEquals(5, request.headers().names().size()); + Assertions.assertEquals("DELETE", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertNull(request.body()); + Assertions.assertNull(request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/sample_one", request.url().toString()); + } + + @Test + void createTerm() throws IOException { + terms.clearParams(); + JSONObject term = Utils.readJson("mockTaxonomy/createTerm.json"); + Request request = terms.create(term).request(); +// Response resp= terms.create(term).execute(); +// System.out.println(resp); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("POST", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertNotNull(request.body()); + Assertions.assertNull(request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/"+ _uid +"/terms", + request.url().toString()); + } + @Test + void fetchAllTerms() throws IOException { + terms.clearParams(); + terms.addParam("limit", 3); + Request request = terms.find().request(); + Response resp = terms.find().execute(); + System.out.println(resp.body().string()); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertNull(request.body()); + Assertions.assertEquals("limit=3", request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/"+ _uid +"/terms?limit=3", + request.url().toString()); + } + + @Test + void fetchSingleTerm() throws IOException { + terms.clearParams(); + Request request = terms.fetch("india").request(); +// Response resp = terms.fetch("india").execute(); +// System.out.println(resp); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertNull(request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/"+ _uid +"/terms/india", + request.url().toString()); + } + + @Test + void updateTerm(){ + JSONObject Newterm = Utils.readJson("mockTaxonomy/updateTerm.json"); + Request request = terms.update(_uid,Newterm).request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("PUT", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertNotNull(request.body()); + Assertions.assertNull(request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/"+ _uid +"/terms/sample_one", + request.url().toString()); + } + + @Test + void deleteTerm(){ + Request request = taxonomy.delete("sample_one").request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("DELETE", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertNull(request.body()); + Assertions.assertNull(request.url().encodedQuery()); + Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/"+ _uid +"", request.url().toString()); + } + + @Test + void ancestorsTerm(){ + terms.clearParams(); + terms.addParam("include_referenced_entries_count", true); + terms.addParam("include_children_count", false); + Request request = terms.ancestors("term_a2").request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertNull(request.body()); + Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", + request.url().encodedQuery()); + Assertions.assertEquals( + "https://api.contentstack.io/v3/taxonomies/"+ _uid +"/terms/term_a2/ancestors?include_children_count=false&include_referenced_entries_count=true", + request.url().toString()); + + } + + @Test + void descendantsTerm(){ + terms.clearParams(); + terms.addParam("include_count", true); + Request request = terms.descendants("term_a2").request(); + Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertNull(request.body()); + Assertions.assertEquals("include_count=true", request.url().encodedQuery()); + Assertions.assertEquals( + "https://api.contentstack.io/v3/taxonomies/"+ _uid +"/terms/term_a2/descendants?include_count=true", + request.url().toString()); + + } + + @Test + void moveTerms(){ + terms.clearParams(); + HashMap headers = new HashMap<>(); + HashMap params = new HashMap<>(); + terms.addHeader("Accept-Encoding", "UTF-8"); + headers.put("Accept-Encoding", "UTF-8"); + params.put("force", true); + terms.addParams(params); + terms.addHeaders(headers); + Request request = terms.reorder(_uid, new JSONObject()).request(); + Assertions.assertEquals(3, request.headers().names().size()); + Assertions.assertEquals("PUT", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals("api.contentstack.io", request.url().host()); + Assertions.assertEquals(6, request.url().pathSegments().size()); + } +} diff --git a/src/test/resources/mocktaxonomy/create.json b/src/test/resources/mocktaxonomy/create.json new file mode 100644 index 00000000..9e0c2a8e --- /dev/null +++ b/src/test/resources/mocktaxonomy/create.json @@ -0,0 +1,8 @@ + +{ + "taxonomy": { + "uid": "sample_one", + "name": "Sample One", + "description": "Description for the sample one taxonomy." + } + } \ No newline at end of file diff --git a/src/test/resources/mocktaxonomy/createTerm.json b/src/test/resources/mocktaxonomy/createTerm.json new file mode 100644 index 00000000..439578d5 --- /dev/null +++ b/src/test/resources/mocktaxonomy/createTerm.json @@ -0,0 +1,8 @@ +{ + "term": { + "uid": "term_a", + "name": "Term A", + "description": "Term A Description for Taxonomy sample", + "parent_uid": null + } + } \ No newline at end of file diff --git a/src/test/resources/mocktaxonomy/update.json b/src/test/resources/mocktaxonomy/update.json new file mode 100644 index 00000000..ece28e56 --- /dev/null +++ b/src/test/resources/mocktaxonomy/update.json @@ -0,0 +1,6 @@ +{ + "taxonomy": { + "name": "Updated Sample One", + "description": "Updated description for the sample one taxonomy." + } + } \ No newline at end of file diff --git a/src/test/resources/mocktaxonomy/updateTerm.json b/src/test/resources/mocktaxonomy/updateTerm.json new file mode 100644 index 00000000..6c7cafb3 --- /dev/null +++ b/src/test/resources/mocktaxonomy/updateTerm.json @@ -0,0 +1,5 @@ +{ + "term": { + "name": "Updated Term A" + } +} \ No newline at end of file From d801a00568b883f74581603cd5d20755e228ff2f Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Thu, 28 Mar 2024 16:20:23 +0530 Subject: [PATCH 027/197] chore: updated changelog.md --- changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/changelog.md b/changelog.md index b21da333..42a19a6b 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## v1.3.2 + +### March 28, 2024 + +- Fix: + - fixed null content type issue + ## v1.3.1 ### Feb 22, 2024 From 25bdfa759344a5074c4d5e9a777e87f722e5e496 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Thu, 4 Apr 2024 12:59:58 +0530 Subject: [PATCH 028/197] v1.3.3 bump --- changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index d869de11..c816953f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,9 +1,9 @@ # Changelog -## v1.3.2 +## v1.3.3 -Fix: - Constructor in Java class files now include missing headers -### Mar 26, 2024 +### Apr 09, 2024 ## v1.3.1 From 110bb737e3e8ae6efaecbcace810a065fb3d85d4 Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Thu, 4 Apr 2024 16:28:52 +0530 Subject: [PATCH 029/197] fix: added fix for gpg signing issue --- .github/workflows/maven-publish.yml | 7 +++++-- pom.xml | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index afd3708e..54561a3c 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -20,9 +20,10 @@ jobs: server-id: ossrh server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} gpg-passphrase: GPG_PASSPHRASE - name: Publish to the Maven Central Repository - run: mvn --batch-mode deploy + run: mvn --batch-mode -Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }} deploy env: MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} @@ -37,7 +38,9 @@ jobs: java-version: "8" distribution: "adopt" server-id: github + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE - name: Publish to GitHub Packages - run: mvn --batch-mode deploy + run: mvn --batch-mode -Dgpg.passphrase=${{ secrets.GPG_PASSPHRASE }} deploy env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/pom.xml b/pom.xml index 9ed6e771..6a4976b3 100644 --- a/pom.xml +++ b/pom.xml @@ -268,6 +268,12 @@ sign + + + --pinentry-mode + loopback + + From 8dc56ec4ad8a781ac190b8d9e56de343a1c82b62 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 12 Apr 2024 12:58:05 +0530 Subject: [PATCH 030/197] added: APISanitytest file ,generating test reports --- pom.xml | 19 +++++- .../cms/organization/OrgApiTests.java | 4 +- .../cms/stack/APISanityTestSuite.java | 26 ++++++++ .../contentstack/cms/stack/AssetAPITest.java | 10 +-- .../cms/stack/ContentTypeAPITest.java | 20 +++--- .../cms/stack/EntryFieldsAPITest.java | 36 +++++------ .../cms/stack/ExtensionAPITest.java | 14 ++--- .../contentstack/cms/stack/RoleAPITest.java | 4 +- .../contentstack/cms/stack/StackAPITest.java | 2 +- .../cms/stack/TaxonomyAPITest.java | 15 +---- .../resources/mockcontenttype/visibility.json | 61 +++++++++++++++++-- 11 files changed, 146 insertions(+), 65 deletions(-) create mode 100644 src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java diff --git a/pom.xml b/pom.xml index 9ed6e771..549f36cf 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.3.2 + 1.3.3 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach @@ -182,6 +182,18 @@ activation 1.1.1
+ + org.junit.platform + junit-platform-runner + 1.10.2 + test + + + org.junit.platform + junit-platform-suite + 1.10.2 + test + @@ -194,7 +206,10 @@ maven-surefire-plugin 3.0.0-M5 - true + + **/*TestSuite.java + + ${project.build.directory}/surefire-reports true diff --git a/src/test/java/com/contentstack/cms/organization/OrgApiTests.java b/src/test/java/com/contentstack/cms/organization/OrgApiTests.java index d835f764..117a973e 100644 --- a/src/test/java/com/contentstack/cms/organization/OrgApiTests.java +++ b/src/test/java/com/contentstack/cms/organization/OrgApiTests.java @@ -15,7 +15,7 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @Tag("unit") -class OrgApiTests { +public class OrgApiTests { private static Organization ORG; private final String ORG_ID = TestClient.ORGANIZATION_UID; @@ -390,7 +390,7 @@ void testAllInvitationWithQuery() throws IOException { .addHeader("api_key", TestClient.API_KEY) .addParam("include_plan", true); Response response = ORG.allInvitations().execute(); - Assertions.assertFalse(response.isSuccessful()); + Assertions.assertTrue(response.isSuccessful()); } } diff --git a/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java b/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java new file mode 100644 index 00000000..a1b6577f --- /dev/null +++ b/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java @@ -0,0 +1,26 @@ +package com.contentstack.cms.stack; +import com.contentstack.cms.organization.OrgApiTests; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.runner.RunWith; + + +@SuppressWarnings("deprecation") +@RunWith(JUnitPlatform.class) +@SelectClasses({ + TaxonomyAPITest.class, + AssetAPITest.class, + ContentTypeAPITest.class, + EntryFieldsAPITest.class, + EnvironmentAPITest.class, + ExtensionAPITest.class, + LocaleAPITest.class, + RoleAPITest.class, + StackAPITest.class, + TokenAPITest.class, + OrgApiTests.class + +}) +public class APISanityTestSuite { + +} diff --git a/src/test/java/com/contentstack/cms/stack/AssetAPITest.java b/src/test/java/com/contentstack/cms/stack/AssetAPITest.java index 6c38a297..9e58d92f 100644 --- a/src/test/java/com/contentstack/cms/stack/AssetAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/AssetAPITest.java @@ -248,10 +248,10 @@ void testAssetDownloadPermanentUrl() throws IOException { @Test void testAssetUploadWithMultipleParams() throws IOException { - String description = "The calender has been placed to assets by ***REMOVED***"; - String filePath = "/Users/shaileshmishra/Documents/workspace/GitHub/contentstack-management-java/src/test/resources/asset.png"; + String description = "The calender has been placed to assets"; + String filePath = "/Users/reeshika.hosmani/Downloads/iot-icon.png"; Contentstack client = new Contentstack.Builder().build(); - Stack stack = client.stack("Your-api-key", "authorization"); + Stack stack = client.stack(API_KEY, MANAGEMENT_TOKEN); Response upload = stack.asset() .addParams(new HashMap<>()) .addHeaders(new HashMap<>()) @@ -261,8 +261,8 @@ void testAssetUploadWithMultipleParams() throws IOException { String[] tags = {"shailesh", "mishra", "mumbai", "india"}; Response uploadMultiple = stack.asset(). uploadAsset(filePath, "parent_uid", "Fake Image", "Something as description", tags).execute(); - Assertions.assertFalse(uploadMultiple.isSuccessful()); - Assertions.assertFalse(upload.isSuccessful()); + Assertions.assertTrue(uploadMultiple.isSuccessful()); + Assertions.assertTrue(upload.isSuccessful()); } } diff --git a/src/test/java/com/contentstack/cms/stack/ContentTypeAPITest.java b/src/test/java/com/contentstack/cms/stack/ContentTypeAPITest.java index 3dda2538..f969995e 100644 --- a/src/test/java/com/contentstack/cms/stack/ContentTypeAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/ContentTypeAPITest.java @@ -14,7 +14,7 @@ class ContentTypeAPITest { public static ContentType contentType; protected static String API_KEY = TestClient.API_KEY; protected static String MANAGEMENT_TOKEN = TestClient.MANAGEMENT_TOKEN; - protected static String contentTypeUid = "contentType"; + protected static String contentTypeUid = "fake_content_type"; protected static Stack stack; private int _COUNT = 2; @@ -96,7 +96,7 @@ void testSingleApi() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type", request.url().toString()); } @Order(5) @@ -113,7 +113,7 @@ void testUpdate() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type", request.url().toString()); } @Order(6) @@ -130,7 +130,7 @@ void testFieldVisibilityRule() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type", request.url().toString()); } @@ -148,7 +148,7 @@ void testReference() { Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.url().encodedQuery()); Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/contentType/references?include_global_fields=false", + "https://api.contentstack.io/v3/content_types/fake_content_type/references?include_global_fields=false", request.url().toString()); } @@ -167,7 +167,7 @@ void testReferenceIncludingGlobalField() { Assertions.assertNotNull(request.url().encodedQuery()); Assertions.assertEquals("include_global_fields=true", request.url().encodedQuery()); Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/contentType/references?include_global_fields=true", + "https://api.contentstack.io/v3/content_types/fake_content_type/references?include_global_fields=true", request.url().toString()); } @@ -184,7 +184,7 @@ void testExport() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType/export", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/export", request.url().toString()); } @@ -202,7 +202,7 @@ void testExportByVersion() { Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.url().encodedQuery()); Assertions.assertEquals("version=1", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType/export?version=1", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/export?version=1", request.url().toString()); } @@ -253,7 +253,7 @@ void testDeleteContentType() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type", request.url().toString()); } @Order(14) @@ -269,7 +269,7 @@ void testDeleteForcefully() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type", request.url().toString()); } } diff --git a/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java b/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java index f35414e2..e3f5ef76 100644 --- a/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java @@ -18,7 +18,7 @@ class EntryFieldsAPITest { private static String API_KEY = TestClient.API_KEY; private static String MANAGEMENT_TOKEN = TestClient.MANAGEMENT_TOKEN; private static ContentType contentType = TestClient.getClient().stack(API_KEY, MANAGEMENT_TOKEN) - .contentType("test"); + .contentType("fake_content_type"); @BeforeAll public static void setup() throws IOException { @@ -37,7 +37,7 @@ void testEntryFindAll() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries", request.url().toString()); } @Order(2) @@ -53,7 +53,7 @@ void testEntryFetch() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + TestClient.API_KEY, + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + TestClient.API_KEY, request.url().toString()); } @@ -74,7 +74,7 @@ void testEntryCreate() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries", request.url().toString()); } @Order(4) @@ -96,7 +96,7 @@ void testUpdate() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + TestClient.API_KEY, + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + TestClient.API_KEY, request.url().toString()); } @@ -119,7 +119,7 @@ void testAtomicOperation() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + TestClient.API_KEY, + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + TestClient.API_KEY, request.url().toString()); } @@ -136,7 +136,7 @@ void testEntryDelete() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY, + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY, request.url().toString()); } @@ -160,7 +160,7 @@ void testEntryVersionName() { Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/test/entries/" + TestClient.API_KEY + "/versions/1/name", + "https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + TestClient.API_KEY + "/versions/1/name", request.url().toString()); } @@ -178,7 +178,7 @@ void testEntryDetailOfAllVersion() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/versions", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/versions", request.url().toString()); } @@ -202,7 +202,7 @@ void testEntryDeleteVersionName() { Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/versions/1/name", + "https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/versions/1/name", request.url().toString()); } @@ -220,7 +220,7 @@ void testEntryGetReference() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/references", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/references", request.url().toString()); } @@ -243,7 +243,7 @@ void testEntryLocalise() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "?locale=en-us", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "?locale=en-us", request.url().toString()); } @@ -260,7 +260,7 @@ void testEntryExport() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/export", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/export", request.url().toString()); } @@ -276,7 +276,7 @@ void testEntryImports() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/import", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/import", request.url().toString()); } @@ -294,7 +294,7 @@ void testEntryImportExisting() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/import", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/import", request.url().toString()); } @@ -317,7 +317,7 @@ void testEntryPublish() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/publish", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/publish", request.url().toString()); } @@ -361,7 +361,7 @@ void testPublishWithReference() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/unpublish", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/unpublish", request.url().toString()); } @@ -378,7 +378,7 @@ public void testEntryQuery() { Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertEquals("product", request.url().pathSegments().get(2)); + Assertions.assertEquals("fake_content_type", request.url().pathSegments().get(2)); Assertions.assertEquals("entries", request.url().pathSegments().get(3)); Assertions.assertNull(request.body()); Assertions.assertEquals("query={\"taxonomies.taxonomy_uid\":\"{ \\\"$in\\\" : [\\\"term_uid1\\\" , \\\"term_uid2\\\" ] }\"}", request.url().query()); diff --git a/src/test/java/com/contentstack/cms/stack/ExtensionAPITest.java b/src/test/java/com/contentstack/cms/stack/ExtensionAPITest.java index 975e9b3b..d4ff4a28 100644 --- a/src/test/java/com/contentstack/cms/stack/ExtensionAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/ExtensionAPITest.java @@ -40,7 +40,7 @@ void extensionGetAll() { extension.addHeader("authtoken", AUTHTOKEN); Request request = extension.find().request(); - Assertions.assertEquals(3, request.headers().names().size()); + Assertions.assertEquals(4, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -60,7 +60,7 @@ void getSingleWithUid() { extension.addHeader("authtoken", AUTHTOKEN); Request request = extension.fetch().request(); - Assertions.assertEquals(3, request.headers().names().size()); + Assertions.assertEquals(4, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -79,7 +79,7 @@ void extensionUpdate() { extension.addHeader("authtoken", AUTHTOKEN); Request request = extension.update(new JSONObject()).request(); - Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -98,7 +98,7 @@ void extensionDelete() { extension.addHeader("authtoken", AUTHTOKEN); Request request = extension.delete().request(); - Assertions.assertEquals(3, request.headers().names().size()); + Assertions.assertEquals(4, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -116,7 +116,7 @@ void extensionGetSingle() { extension.addHeader("authtoken", AUTHTOKEN); Request request = extension.fetch().request(); - Assertions.assertEquals(3, request.headers().names().size()); + Assertions.assertEquals(4, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -149,7 +149,7 @@ public void writeTo(BufferedSink sink) { param.put("include_branch", false); Request request = extension.uploadCustomField(params).request(); - Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -169,7 +169,7 @@ void extensionDeleteAgain() { extension.addHeader("authtoken", AUTHTOKEN); Request request = extension.delete().request(); - Assertions.assertEquals(3, request.headers().names().size()); + Assertions.assertEquals(4, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); diff --git a/src/test/java/com/contentstack/cms/stack/RoleAPITest.java b/src/test/java/com/contentstack/cms/stack/RoleAPITest.java index c7966f5c..c22260a6 100644 --- a/src/test/java/com/contentstack/cms/stack/RoleAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/RoleAPITest.java @@ -84,7 +84,7 @@ void updateRole() { object.put("key", "value"); Request request = roles.update(object).request(); - Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -101,7 +101,7 @@ void deleteRole() { roles.addHeader("authtoken", AUTHTOKEN); roles.addHeader("api_key", API_KEY); Request request = roles.delete().request(); - Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); diff --git a/src/test/java/com/contentstack/cms/stack/StackAPITest.java b/src/test/java/com/contentstack/cms/stack/StackAPITest.java index 38d40078..7e7b5222 100644 --- a/src/test/java/com/contentstack/cms/stack/StackAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/StackAPITest.java @@ -158,7 +158,7 @@ void testStackTransferOwnership() { assert response.errorBody() != null; Error error = new Gson().fromJson(response.errorBody().string(), Error.class); int errCode = error.getErrorCode(); - Assertions.assertEquals(309, errCode); + Assertions.assertEquals(105, errCode); } } catch (IOException e) { log.warning(e.getLocalizedMessage()); diff --git a/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java b/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java index 197f1083..51b50737 100644 --- a/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java @@ -44,7 +44,6 @@ void fetchAll(){ try { Response response = taxonomy.find().execute(); - System.out.println(response.body().string()); if (response.isSuccessful()) { Assertions.assertTrue(response.isSuccessful()); } else { @@ -59,7 +58,6 @@ void fetchAll(){ void fetchSingle() { try { Response response = taxonomy.fetch("regions").execute(); - System.out.println(response); if (response.isSuccessful()) { Assertions.assertTrue(response.isSuccessful()); } else { @@ -77,9 +75,6 @@ void createTaxonomy() throws IOException { taxonomy.addHeader(Util.API_KEY, API_KEY); taxonomy.addHeader(Util.AUTHORIZATION, MANAGEMENT_TOKEN); Request request = taxonomy.create(requestBody).request(); - Response resp = taxonomy.create(requestBody).execute(); - System.out.println(resp); - System.out.println(request); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); Assertions.assertTrue(request.url().isHttps()); @@ -98,7 +93,6 @@ void updateTaxonomy() { taxonomy.addHeader(Util.API_KEY, API_KEY); taxonomy.addHeader(Util.AUTHORIZATION, MANAGEMENT_TOKEN); Request request = taxonomy.update("sample_one",updateBody).request(); - System.out.println(request); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); Assertions.assertTrue(request.url().isHttps()); @@ -124,7 +118,6 @@ void deleteTaxonomy(){ taxonomy.addHeaders(headers); taxonomy.addParams(params); Request request = taxonomy.delete("sample_one").request(); - System.out.println(request); Assertions.assertEquals(5, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); Assertions.assertTrue(request.url().isHttps()); @@ -141,9 +134,7 @@ void createTerm() throws IOException { terms.clearParams(); JSONObject term = Utils.readJson("mockTaxonomy/createTerm.json"); Request request = terms.create(term).request(); -// Response resp= terms.create(term).execute(); -// System.out.println(resp); - Assertions.assertEquals(2, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -162,8 +153,6 @@ void fetchAllTerms() throws IOException { terms.clearParams(); terms.addParam("limit", 3); Request request = terms.find().request(); - Response resp = terms.find().execute(); - System.out.println(resp.body().string()); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); @@ -183,8 +172,6 @@ void fetchAllTerms() throws IOException { void fetchSingleTerm() throws IOException { terms.clearParams(); Request request = terms.fetch("india").request(); -// Response resp = terms.fetch("india").execute(); -// System.out.println(resp); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); diff --git a/src/test/resources/mockcontenttype/visibility.json b/src/test/resources/mockcontenttype/visibility.json index 00d45e3d..8e1391df 100644 --- a/src/test/resources/mockcontenttype/visibility.json +++ b/src/test/resources/mockcontenttype/visibility.json @@ -1,16 +1,69 @@ { "content_type": { + "title": "fake_content_type", + "uid": "fake_content_type", + "schema": [{ + "display_name": "Title", + "uid": "title", + "data_type": "text", + "field_metadata": { + "_default": true + }, + "unique": false, + "mandatory": true, + "multiple": false + }, + { + "display_name": "URL", + "uid": "url", + "data_type": "text", + "field_metadata": { + "_default": true + }, + "unique": false, + "multiple": false + }, + { + "display_name": "Single Line Textbox", + "uid": "single_line_textbox", + "data_type": "text", + "field_metadata": { + "_default": true + }, + "unique": false, + "multiple": false + }, + { + "display_name": "Multi Line Textbox", + "uid": "multi_line_textbox", + "data_type": "text", + "field_metadata": { + "_default": true + }, + "unique": false, + "multiple": false + } + ], "field_rules": [{ "conditions": [{ - "operand_field": "operand_field_uid", + "operand_field": "single_line_textbox", "operator": "equals", - "value": "value_corresponding_to_operator" + "value": "abc" }], "match_type": "all", "actions": [{ "action": "show", - "target_field": "target_field_uid" + "target_field": "multi_line_textbox" }] - }] + }], + "options": { + "title": "title", + "publishable": true, + "is_page": true, + "singleton": false, + "sub_title": ["url"], + "url_pattern": "/:title", + "url_prefix": "/" + } } } \ No newline at end of file From e73ec3df7fb99e3b879261ecdb2dc256987d1bbb Mon Sep 17 00:00:00 2001 From: reeshika-h <157372605+reeshika-h@users.noreply.github.com> Date: Fri, 12 Apr 2024 17:23:41 +0530 Subject: [PATCH 031/197] Revert "added: APISanitytest file ,generating test reports" --- pom.xml | 19 +----- .../cms/organization/OrgApiTests.java | 4 +- .../cms/stack/APISanityTestSuite.java | 26 -------- .../contentstack/cms/stack/AssetAPITest.java | 10 +-- .../cms/stack/ContentTypeAPITest.java | 20 +++--- .../cms/stack/EntryFieldsAPITest.java | 36 +++++------ .../cms/stack/ExtensionAPITest.java | 14 ++--- .../contentstack/cms/stack/RoleAPITest.java | 4 +- .../contentstack/cms/stack/StackAPITest.java | 2 +- .../cms/stack/TaxonomyAPITest.java | 15 ++++- .../resources/mockcontenttype/visibility.json | 61 ++----------------- 11 files changed, 65 insertions(+), 146 deletions(-) delete mode 100644 src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java diff --git a/pom.xml b/pom.xml index 549f36cf..9ed6e771 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.3.3 + 1.3.2 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach @@ -182,18 +182,6 @@ activation 1.1.1 - - org.junit.platform - junit-platform-runner - 1.10.2 - test - - - org.junit.platform - junit-platform-suite - 1.10.2 - test - @@ -206,10 +194,7 @@ maven-surefire-plugin 3.0.0-M5 - - **/*TestSuite.java - - ${project.build.directory}/surefire-reports + true true diff --git a/src/test/java/com/contentstack/cms/organization/OrgApiTests.java b/src/test/java/com/contentstack/cms/organization/OrgApiTests.java index 117a973e..d835f764 100644 --- a/src/test/java/com/contentstack/cms/organization/OrgApiTests.java +++ b/src/test/java/com/contentstack/cms/organization/OrgApiTests.java @@ -15,7 +15,7 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @Tag("unit") -public class OrgApiTests { +class OrgApiTests { private static Organization ORG; private final String ORG_ID = TestClient.ORGANIZATION_UID; @@ -390,7 +390,7 @@ void testAllInvitationWithQuery() throws IOException { .addHeader("api_key", TestClient.API_KEY) .addParam("include_plan", true); Response response = ORG.allInvitations().execute(); - Assertions.assertTrue(response.isSuccessful()); + Assertions.assertFalse(response.isSuccessful()); } } diff --git a/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java b/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java deleted file mode 100644 index a1b6577f..00000000 --- a/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.contentstack.cms.stack; -import com.contentstack.cms.organization.OrgApiTests; -import org.junit.platform.runner.JUnitPlatform; -import org.junit.platform.suite.api.SelectClasses; -import org.junit.runner.RunWith; - - -@SuppressWarnings("deprecation") -@RunWith(JUnitPlatform.class) -@SelectClasses({ - TaxonomyAPITest.class, - AssetAPITest.class, - ContentTypeAPITest.class, - EntryFieldsAPITest.class, - EnvironmentAPITest.class, - ExtensionAPITest.class, - LocaleAPITest.class, - RoleAPITest.class, - StackAPITest.class, - TokenAPITest.class, - OrgApiTests.class - -}) -public class APISanityTestSuite { - -} diff --git a/src/test/java/com/contentstack/cms/stack/AssetAPITest.java b/src/test/java/com/contentstack/cms/stack/AssetAPITest.java index 9e58d92f..6c38a297 100644 --- a/src/test/java/com/contentstack/cms/stack/AssetAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/AssetAPITest.java @@ -248,10 +248,10 @@ void testAssetDownloadPermanentUrl() throws IOException { @Test void testAssetUploadWithMultipleParams() throws IOException { - String description = "The calender has been placed to assets"; - String filePath = "/Users/reeshika.hosmani/Downloads/iot-icon.png"; + String description = "The calender has been placed to assets by ***REMOVED***"; + String filePath = "/Users/shaileshmishra/Documents/workspace/GitHub/contentstack-management-java/src/test/resources/asset.png"; Contentstack client = new Contentstack.Builder().build(); - Stack stack = client.stack(API_KEY, MANAGEMENT_TOKEN); + Stack stack = client.stack("Your-api-key", "authorization"); Response upload = stack.asset() .addParams(new HashMap<>()) .addHeaders(new HashMap<>()) @@ -261,8 +261,8 @@ void testAssetUploadWithMultipleParams() throws IOException { String[] tags = {"shailesh", "mishra", "mumbai", "india"}; Response uploadMultiple = stack.asset(). uploadAsset(filePath, "parent_uid", "Fake Image", "Something as description", tags).execute(); - Assertions.assertTrue(uploadMultiple.isSuccessful()); - Assertions.assertTrue(upload.isSuccessful()); + Assertions.assertFalse(uploadMultiple.isSuccessful()); + Assertions.assertFalse(upload.isSuccessful()); } } diff --git a/src/test/java/com/contentstack/cms/stack/ContentTypeAPITest.java b/src/test/java/com/contentstack/cms/stack/ContentTypeAPITest.java index f969995e..3dda2538 100644 --- a/src/test/java/com/contentstack/cms/stack/ContentTypeAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/ContentTypeAPITest.java @@ -14,7 +14,7 @@ class ContentTypeAPITest { public static ContentType contentType; protected static String API_KEY = TestClient.API_KEY; protected static String MANAGEMENT_TOKEN = TestClient.MANAGEMENT_TOKEN; - protected static String contentTypeUid = "fake_content_type"; + protected static String contentTypeUid = "contentType"; protected static Stack stack; private int _COUNT = 2; @@ -96,7 +96,7 @@ void testSingleApi() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); } @Order(5) @@ -113,7 +113,7 @@ void testUpdate() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); } @Order(6) @@ -130,7 +130,7 @@ void testFieldVisibilityRule() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); } @@ -148,7 +148,7 @@ void testReference() { Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.url().encodedQuery()); Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/fake_content_type/references?include_global_fields=false", + "https://api.contentstack.io/v3/content_types/contentType/references?include_global_fields=false", request.url().toString()); } @@ -167,7 +167,7 @@ void testReferenceIncludingGlobalField() { Assertions.assertNotNull(request.url().encodedQuery()); Assertions.assertEquals("include_global_fields=true", request.url().encodedQuery()); Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/fake_content_type/references?include_global_fields=true", + "https://api.contentstack.io/v3/content_types/contentType/references?include_global_fields=true", request.url().toString()); } @@ -184,7 +184,7 @@ void testExport() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/export", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType/export", request.url().toString()); } @@ -202,7 +202,7 @@ void testExportByVersion() { Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.url().encodedQuery()); Assertions.assertEquals("version=1", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/export?version=1", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType/export?version=1", request.url().toString()); } @@ -253,7 +253,7 @@ void testDeleteContentType() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); } @Order(14) @@ -269,7 +269,7 @@ void testDeleteForcefully() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); } } diff --git a/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java b/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java index e3f5ef76..f35414e2 100644 --- a/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java @@ -18,7 +18,7 @@ class EntryFieldsAPITest { private static String API_KEY = TestClient.API_KEY; private static String MANAGEMENT_TOKEN = TestClient.MANAGEMENT_TOKEN; private static ContentType contentType = TestClient.getClient().stack(API_KEY, MANAGEMENT_TOKEN) - .contentType("fake_content_type"); + .contentType("test"); @BeforeAll public static void setup() throws IOException { @@ -37,7 +37,7 @@ void testEntryFindAll() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries", request.url().toString()); } @Order(2) @@ -53,7 +53,7 @@ void testEntryFetch() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + TestClient.API_KEY, + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + TestClient.API_KEY, request.url().toString()); } @@ -74,7 +74,7 @@ void testEntryCreate() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries", request.url().toString()); + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries", request.url().toString()); } @Order(4) @@ -96,7 +96,7 @@ void testUpdate() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + TestClient.API_KEY, + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + TestClient.API_KEY, request.url().toString()); } @@ -119,7 +119,7 @@ void testAtomicOperation() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + TestClient.API_KEY, + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + TestClient.API_KEY, request.url().toString()); } @@ -136,7 +136,7 @@ void testEntryDelete() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY, + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY, request.url().toString()); } @@ -160,7 +160,7 @@ void testEntryVersionName() { Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + TestClient.API_KEY + "/versions/1/name", + "https://api.contentstack.io/v3/content_types/test/entries/" + TestClient.API_KEY + "/versions/1/name", request.url().toString()); } @@ -178,7 +178,7 @@ void testEntryDetailOfAllVersion() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/versions", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/versions", request.url().toString()); } @@ -202,7 +202,7 @@ void testEntryDeleteVersionName() { Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/versions/1/name", + "https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/versions/1/name", request.url().toString()); } @@ -220,7 +220,7 @@ void testEntryGetReference() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/references", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/references", request.url().toString()); } @@ -243,7 +243,7 @@ void testEntryLocalise() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "?locale=en-us", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "?locale=en-us", request.url().toString()); } @@ -260,7 +260,7 @@ void testEntryExport() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/export", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/export", request.url().toString()); } @@ -276,7 +276,7 @@ void testEntryImports() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/import", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/import", request.url().toString()); } @@ -294,7 +294,7 @@ void testEntryImportExisting() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/import", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/import", request.url().toString()); } @@ -317,7 +317,7 @@ void testEntryPublish() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/publish", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/publish", request.url().toString()); } @@ -361,7 +361,7 @@ void testPublishWithReference() { Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/fake_content_type/entries/" + API_KEY + "/unpublish", + Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/unpublish", request.url().toString()); } @@ -378,7 +378,7 @@ public void testEntryQuery() { Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertEquals("fake_content_type", request.url().pathSegments().get(2)); + Assertions.assertEquals("product", request.url().pathSegments().get(2)); Assertions.assertEquals("entries", request.url().pathSegments().get(3)); Assertions.assertNull(request.body()); Assertions.assertEquals("query={\"taxonomies.taxonomy_uid\":\"{ \\\"$in\\\" : [\\\"term_uid1\\\" , \\\"term_uid2\\\" ] }\"}", request.url().query()); diff --git a/src/test/java/com/contentstack/cms/stack/ExtensionAPITest.java b/src/test/java/com/contentstack/cms/stack/ExtensionAPITest.java index d4ff4a28..975e9b3b 100644 --- a/src/test/java/com/contentstack/cms/stack/ExtensionAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/ExtensionAPITest.java @@ -40,7 +40,7 @@ void extensionGetAll() { extension.addHeader("authtoken", AUTHTOKEN); Request request = extension.find().request(); - Assertions.assertEquals(4, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -60,7 +60,7 @@ void getSingleWithUid() { extension.addHeader("authtoken", AUTHTOKEN); Request request = extension.fetch().request(); - Assertions.assertEquals(4, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -79,7 +79,7 @@ void extensionUpdate() { extension.addHeader("authtoken", AUTHTOKEN); Request request = extension.update(new JSONObject()).request(); - Assertions.assertEquals(3, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -98,7 +98,7 @@ void extensionDelete() { extension.addHeader("authtoken", AUTHTOKEN); Request request = extension.delete().request(); - Assertions.assertEquals(4, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -116,7 +116,7 @@ void extensionGetSingle() { extension.addHeader("authtoken", AUTHTOKEN); Request request = extension.fetch().request(); - Assertions.assertEquals(4, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -149,7 +149,7 @@ public void writeTo(BufferedSink sink) { param.put("include_branch", false); Request request = extension.uploadCustomField(params).request(); - Assertions.assertEquals(3, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -169,7 +169,7 @@ void extensionDeleteAgain() { extension.addHeader("authtoken", AUTHTOKEN); Request request = extension.delete().request(); - Assertions.assertEquals(4, request.headers().names().size()); + Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); diff --git a/src/test/java/com/contentstack/cms/stack/RoleAPITest.java b/src/test/java/com/contentstack/cms/stack/RoleAPITest.java index c22260a6..c7966f5c 100644 --- a/src/test/java/com/contentstack/cms/stack/RoleAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/RoleAPITest.java @@ -84,7 +84,7 @@ void updateRole() { object.put("key", "value"); Request request = roles.update(object).request(); - Assertions.assertEquals(3, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -101,7 +101,7 @@ void deleteRole() { roles.addHeader("authtoken", AUTHTOKEN); roles.addHeader("api_key", API_KEY); Request request = roles.delete().request(); - Assertions.assertEquals(3, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); diff --git a/src/test/java/com/contentstack/cms/stack/StackAPITest.java b/src/test/java/com/contentstack/cms/stack/StackAPITest.java index 7e7b5222..38d40078 100644 --- a/src/test/java/com/contentstack/cms/stack/StackAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/StackAPITest.java @@ -158,7 +158,7 @@ void testStackTransferOwnership() { assert response.errorBody() != null; Error error = new Gson().fromJson(response.errorBody().string(), Error.class); int errCode = error.getErrorCode(); - Assertions.assertEquals(105, errCode); + Assertions.assertEquals(309, errCode); } } catch (IOException e) { log.warning(e.getLocalizedMessage()); diff --git a/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java b/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java index 51b50737..197f1083 100644 --- a/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java @@ -44,6 +44,7 @@ void fetchAll(){ try { Response response = taxonomy.find().execute(); + System.out.println(response.body().string()); if (response.isSuccessful()) { Assertions.assertTrue(response.isSuccessful()); } else { @@ -58,6 +59,7 @@ void fetchAll(){ void fetchSingle() { try { Response response = taxonomy.fetch("regions").execute(); + System.out.println(response); if (response.isSuccessful()) { Assertions.assertTrue(response.isSuccessful()); } else { @@ -75,6 +77,9 @@ void createTaxonomy() throws IOException { taxonomy.addHeader(Util.API_KEY, API_KEY); taxonomy.addHeader(Util.AUTHORIZATION, MANAGEMENT_TOKEN); Request request = taxonomy.create(requestBody).request(); + Response resp = taxonomy.create(requestBody).execute(); + System.out.println(resp); + System.out.println(request); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); Assertions.assertTrue(request.url().isHttps()); @@ -93,6 +98,7 @@ void updateTaxonomy() { taxonomy.addHeader(Util.API_KEY, API_KEY); taxonomy.addHeader(Util.AUTHORIZATION, MANAGEMENT_TOKEN); Request request = taxonomy.update("sample_one",updateBody).request(); + System.out.println(request); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); Assertions.assertTrue(request.url().isHttps()); @@ -118,6 +124,7 @@ void deleteTaxonomy(){ taxonomy.addHeaders(headers); taxonomy.addParams(params); Request request = taxonomy.delete("sample_one").request(); + System.out.println(request); Assertions.assertEquals(5, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); Assertions.assertTrue(request.url().isHttps()); @@ -134,7 +141,9 @@ void createTerm() throws IOException { terms.clearParams(); JSONObject term = Utils.readJson("mockTaxonomy/createTerm.json"); Request request = terms.create(term).request(); - Assertions.assertEquals(3, request.headers().names().size()); +// Response resp= terms.create(term).execute(); +// System.out.println(resp); + Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -153,6 +162,8 @@ void fetchAllTerms() throws IOException { terms.clearParams(); terms.addParam("limit", 3); Request request = terms.find().request(); + Response resp = terms.find().execute(); + System.out.println(resp.body().string()); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); @@ -172,6 +183,8 @@ void fetchAllTerms() throws IOException { void fetchSingleTerm() throws IOException { terms.clearParams(); Request request = terms.fetch("india").request(); +// Response resp = terms.fetch("india").execute(); +// System.out.println(resp); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); diff --git a/src/test/resources/mockcontenttype/visibility.json b/src/test/resources/mockcontenttype/visibility.json index 8e1391df..00d45e3d 100644 --- a/src/test/resources/mockcontenttype/visibility.json +++ b/src/test/resources/mockcontenttype/visibility.json @@ -1,69 +1,16 @@ { "content_type": { - "title": "fake_content_type", - "uid": "fake_content_type", - "schema": [{ - "display_name": "Title", - "uid": "title", - "data_type": "text", - "field_metadata": { - "_default": true - }, - "unique": false, - "mandatory": true, - "multiple": false - }, - { - "display_name": "URL", - "uid": "url", - "data_type": "text", - "field_metadata": { - "_default": true - }, - "unique": false, - "multiple": false - }, - { - "display_name": "Single Line Textbox", - "uid": "single_line_textbox", - "data_type": "text", - "field_metadata": { - "_default": true - }, - "unique": false, - "multiple": false - }, - { - "display_name": "Multi Line Textbox", - "uid": "multi_line_textbox", - "data_type": "text", - "field_metadata": { - "_default": true - }, - "unique": false, - "multiple": false - } - ], "field_rules": [{ "conditions": [{ - "operand_field": "single_line_textbox", + "operand_field": "operand_field_uid", "operator": "equals", - "value": "abc" + "value": "value_corresponding_to_operator" }], "match_type": "all", "actions": [{ "action": "show", - "target_field": "multi_line_textbox" + "target_field": "target_field_uid" }] - }], - "options": { - "title": "title", - "publishable": true, - "is_page": true, - "singleton": false, - "sub_title": ["url"], - "url_pattern": "/:title", - "url_prefix": "/" - } + }] } } \ No newline at end of file From 1c406646dbc6036599d3658c2edb7ddf112ba13a Mon Sep 17 00:00:00 2001 From: reeshika-h <157372605+reeshika-h@users.noreply.github.com> Date: Wed, 17 Apr 2024 13:36:02 +0530 Subject: [PATCH 032/197] Merging :"Fix: Constructor in Java class files now include missing headers" (#53) * passed headers * added: changes in setup of testcases * added the new version in the logs * added: taxonomyAPI testcases along with mocktaxonomy folder * v1.3.3 bump * added: APISanitytest file ,generating test reports * Revert "added: APISanitytest file ,generating test reports" --------- Co-authored-by: reeshika-h --- changelog.md | 5 + .../com/contentstack/cms/stack/Alias.java | 6 +- .../com/contentstack/cms/stack/Asset.java | 4 +- .../com/contentstack/cms/stack/AuditLog.java | 7 +- .../com/contentstack/cms/stack/Branch.java | 6 +- .../contentstack/cms/stack/BulkOperation.java | 4 +- .../contentstack/cms/stack/DeliveryToken.java | 7 +- .../contentstack/cms/stack/Environment.java | 6 +- .../contentstack/cms/stack/Extensions.java | 6 +- .../com/contentstack/cms/stack/Folder.java | 6 +- .../contentstack/cms/stack/GlobalField.java | 7 +- .../com/contentstack/cms/stack/Label.java | 7 +- .../com/contentstack/cms/stack/Locale.java | 6 +- .../cms/stack/ManagementToken.java | 7 +- .../contentstack/cms/stack/PublishQueue.java | 7 +- .../com/contentstack/cms/stack/Release.java | 9 +- .../contentstack/cms/stack/ReleaseItem.java | 4 +- .../com/contentstack/cms/stack/Roles.java | 7 +- .../com/contentstack/cms/stack/Stack.java | 56 ++-- .../com/contentstack/cms/stack/Taxonomy.java | 2 + .../contentstack/cms/stack/TokenService.java | 20 +- .../com/contentstack/cms/stack/Tokens.java | 14 +- .../com/contentstack/cms/stack/Webhook.java | 7 +- .../com/contentstack/cms/stack/Workflow.java | 7 +- .../cms/stack/EnvironmentAPITest.java | 20 +- .../contentstack/cms/stack/LocaleAPITest.java | 9 +- .../cms/stack/TaxonomyAPITest.java | 300 ++++++++++++++++++ .../contentstack/cms/stack/TokenUnitTest.java | 24 +- src/test/resources/mocktaxonomy/create.json | 8 + .../resources/mocktaxonomy/createTerm.json | 8 + src/test/resources/mocktaxonomy/update.json | 6 + .../resources/mocktaxonomy/updateTerm.json | 5 + 32 files changed, 496 insertions(+), 101 deletions(-) create mode 100644 src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java create mode 100644 src/test/resources/mocktaxonomy/create.json create mode 100644 src/test/resources/mocktaxonomy/createTerm.json create mode 100644 src/test/resources/mocktaxonomy/update.json create mode 100644 src/test/resources/mocktaxonomy/updateTerm.json diff --git a/changelog.md b/changelog.md index 42a19a6b..2004b85f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,10 @@ # Changelog +## v1.3.3 +-Fix: + - Constructor in Java class files now include missing headers +### Apr 09, 2024 + ## v1.3.2 ### March 28, 2024 diff --git a/src/main/java/com/contentstack/cms/stack/Alias.java b/src/main/java/com/contentstack/cms/stack/Alias.java index 906f32ad..c074fc1d 100644 --- a/src/main/java/com/contentstack/cms/stack/Alias.java +++ b/src/main/java/com/contentstack/cms/stack/Alias.java @@ -35,8 +35,9 @@ public class Alias implements BaseImplementation { // also creates an // instance of the `AliasService` interface using the provided `Retrofit` // instance. - protected Alias(Retrofit instance) { + protected Alias(Retrofit instance,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.headers.put("Content-Type", "application/json"); params = new HashMap<>(); this.service = instance.create(AliasService.class); @@ -45,8 +46,9 @@ protected Alias(Retrofit instance) { // The `protected Alias(Retrofit instance, String aliasUid)` constructor is used // to create an // instance of the `Alias` class with a specific alias UID. - protected Alias(Retrofit instance, String aliasUid) { + protected Alias(Retrofit instance,Map headers, String aliasUid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.headers.put("Content-Type", "application/json"); params = new HashMap<>(); this.uid = aliasUid; diff --git a/src/main/java/com/contentstack/cms/stack/Asset.java b/src/main/java/com/contentstack/cms/stack/Asset.java index c0b93435..c78abb87 100644 --- a/src/main/java/com/contentstack/cms/stack/Asset.java +++ b/src/main/java/com/contentstack/cms/stack/Asset.java @@ -117,7 +117,7 @@ protected void clearParams() { * @return Folder */ public Folder folder() { - return new Folder(this.instance); + return new Folder(this.instance,this.headers); } /** @@ -127,7 +127,7 @@ public Folder folder() { * @return Folder */ public Folder folder(@NotNull String folderUid) { - return new Folder(this.instance, folderUid); + return new Folder(this.instance,this.headers, folderUid); } /** diff --git a/src/main/java/com/contentstack/cms/stack/AuditLog.java b/src/main/java/com/contentstack/cms/stack/AuditLog.java index eac83421..d7ce42d3 100644 --- a/src/main/java/com/contentstack/cms/stack/AuditLog.java +++ b/src/main/java/com/contentstack/cms/stack/AuditLog.java @@ -7,6 +7,7 @@ import retrofit2.Retrofit; import java.util.HashMap; +import java.util.Map; import java.util.Objects; /** @@ -37,14 +38,16 @@ public class AuditLog implements BaseImplementation { protected HashMap params; private String logItemUid; - protected AuditLog(Retrofit retrofit) { + protected AuditLog(Retrofit retrofit,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.service = retrofit.create(AuditLogService.class); } - protected AuditLog(Retrofit retrofit, String uid) { + protected AuditLog(Retrofit retrofit,Map headers, String uid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.logItemUid = uid; this.service = retrofit.create(AuditLogService.class); diff --git a/src/main/java/com/contentstack/cms/stack/Branch.java b/src/main/java/com/contentstack/cms/stack/Branch.java index 68254d96..f4572d57 100644 --- a/src/main/java/com/contentstack/cms/stack/Branch.java +++ b/src/main/java/com/contentstack/cms/stack/Branch.java @@ -34,15 +34,17 @@ public class Branch implements BaseImplementation { private Retrofit instance; private String baseBranchId; - protected Branch(Retrofit instance) { + protected Branch(Retrofit instance,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.headers.put("Content-Type", "application/json"); this.params = new HashMap<>(); this.service = instance.create(BranchService.class); } - protected Branch(Retrofit instance, String uid) { + protected Branch(Retrofit instance,Map headers, String uid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.headers.put("Content-Type", "application/json"); this.baseBranchId = uid; this.params = new HashMap<>(); diff --git a/src/main/java/com/contentstack/cms/stack/BulkOperation.java b/src/main/java/com/contentstack/cms/stack/BulkOperation.java index 2e49ed32..52bcb1d8 100644 --- a/src/main/java/com/contentstack/cms/stack/BulkOperation.java +++ b/src/main/java/com/contentstack/cms/stack/BulkOperation.java @@ -8,6 +8,7 @@ import retrofit2.Retrofit; import java.util.HashMap; +import java.util.Map; /** * You can perform bulk operations such as Publish, Unpublished, and Delete on @@ -48,8 +49,9 @@ public class BulkOperation implements BaseImplementation { * * @param retrofit the retrofit */ - protected BulkOperation(Retrofit retrofit) { + protected BulkOperation(Retrofit retrofit,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.retrofit = retrofit; this.service = this.retrofit.create(BulkOperationService.class); diff --git a/src/main/java/com/contentstack/cms/stack/DeliveryToken.java b/src/main/java/com/contentstack/cms/stack/DeliveryToken.java index fe470d5d..f0aa8cc5 100644 --- a/src/main/java/com/contentstack/cms/stack/DeliveryToken.java +++ b/src/main/java/com/contentstack/cms/stack/DeliveryToken.java @@ -7,6 +7,7 @@ import retrofit2.Call; import java.util.HashMap; +import java.util.Map; import java.util.Objects; /** @@ -31,14 +32,16 @@ public class DeliveryToken implements BaseImplementation { private String tokenUid; String ERROR = "Token UID Can Not Be Null OR Empty"; - protected DeliveryToken(TokenService service) { + protected DeliveryToken(TokenService service, Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.service = service; } - protected DeliveryToken(TokenService service, @NotNull String tokenUid) { + protected DeliveryToken(TokenService service, Map headers, @NotNull String tokenUid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.tokenUid = tokenUid; this.service = service; diff --git a/src/main/java/com/contentstack/cms/stack/Environment.java b/src/main/java/com/contentstack/cms/stack/Environment.java index 29b39a33..8ef0c9d2 100644 --- a/src/main/java/com/contentstack/cms/stack/Environment.java +++ b/src/main/java/com/contentstack/cms/stack/Environment.java @@ -27,15 +27,17 @@ public class Environment implements BaseImplementation { protected final EnvironmentService service; protected String environment; - protected Environment(Retrofit instance) { + protected Environment(Retrofit instance, Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.service = instance.create(EnvironmentService.class); } - protected Environment(Retrofit instance, String environment) { + protected Environment(Retrofit instance, Map headers, String environment) { this.environment = environment; this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.service = instance.create(EnvironmentService.class); } diff --git a/src/main/java/com/contentstack/cms/stack/Extensions.java b/src/main/java/com/contentstack/cms/stack/Extensions.java index 8ccb3f57..437c8cab 100644 --- a/src/main/java/com/contentstack/cms/stack/Extensions.java +++ b/src/main/java/com/contentstack/cms/stack/Extensions.java @@ -36,14 +36,16 @@ public class Extensions implements BaseImplementation { protected HashMap params; protected String customFieldUid; - protected Extensions(Retrofit retrofit) { + protected Extensions(Retrofit retrofit,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.service = retrofit.create(ExtensionsService.class); } - protected Extensions(Retrofit retrofit, String fieldUid) { + protected Extensions(Retrofit retrofit,Map headers, String fieldUid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.customFieldUid = fieldUid; this.service = retrofit.create(ExtensionsService.class); diff --git a/src/main/java/com/contentstack/cms/stack/Folder.java b/src/main/java/com/contentstack/cms/stack/Folder.java index b8211427..a249b598 100644 --- a/src/main/java/com/contentstack/cms/stack/Folder.java +++ b/src/main/java/com/contentstack/cms/stack/Folder.java @@ -18,14 +18,16 @@ public class Folder implements BaseImplementation { protected final AssetService service; private String folderUid; - protected Folder(Retrofit instance) { + protected Folder(Retrofit instance,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); params = new HashMap<>(); this.service = instance.create(AssetService.class); } - protected Folder(Retrofit instance, String folderUid) { + protected Folder(Retrofit instance,Map headers, String folderUid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); params = new HashMap<>(); this.folderUid = folderUid; this.service = instance.create(AssetService.class); diff --git a/src/main/java/com/contentstack/cms/stack/GlobalField.java b/src/main/java/com/contentstack/cms/stack/GlobalField.java index ae78877d..47ad9599 100644 --- a/src/main/java/com/contentstack/cms/stack/GlobalField.java +++ b/src/main/java/com/contentstack/cms/stack/GlobalField.java @@ -8,6 +8,7 @@ import retrofit2.Retrofit; import java.util.HashMap; +import java.util.Map; /** * A Global field is a reusable field (or group of fields) that you can define @@ -37,14 +38,16 @@ public class GlobalField implements BaseImplementation { protected HashMap params; protected String globalFiledUid; - protected GlobalField(Retrofit retrofit) { + protected GlobalField(Retrofit retrofit,Map headers) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.service = retrofit.create(GlobalFieldService.class); } - protected GlobalField(Retrofit retrofit, String uid) { + protected GlobalField(Retrofit retrofit,Map headers, String uid) { this.headers = new HashMap<>(); + this.headers.putAll(headers); this.params = new HashMap<>(); this.globalFiledUid = uid; this.service = retrofit.create(GlobalFieldService.class); diff --git a/src/main/java/com/contentstack/cms/stack/Label.java b/src/main/java/com/contentstack/cms/stack/Label.java index 364e9ae9..d6b3943c 100644 --- a/src/main/java/com/contentstack/cms/stack/Label.java +++ b/src/main/java/com/contentstack/cms/stack/Label.java @@ -8,6 +8,7 @@ import retrofit2.Retrofit; import java.util.HashMap; +import java.util.Map; /** * Labels allow you to group a collection of content within a stack. Using @@ -32,15 +33,17 @@ public class Label implements BaseImplementation
*/ public Organization organization(@NotNull String organizationUid) { - Objects.requireNonNull(this.authtoken, "Please Login to access user instance"); - if (organizationUid.isEmpty()) + if (!isOAuthConfigured() && this.authtoken == null) { + throw new IllegalStateException("Please login or configure OAuth to access organization"); + } + if (organizationUid.isEmpty()) { throw new IllegalStateException("organizationUid can not be empty"); + } return new Organization(this.instance, organizationUid); } @@ -339,7 +345,9 @@ public Organization organization(@NotNull String organizationUid) { * @return the stack instance */ public Stack stack() { - Objects.requireNonNull(this.authtoken, ILLEGAL_USER); + if (!isOAuthConfigured() && this.authtoken == null) { + throw new IllegalStateException("Please login or configure OAuth to access stack"); + } return new Stack(this.instance); } @@ -362,8 +370,9 @@ public Stack stack() { * @return the stack instance */ public Stack stack(@NotNull Map header) { - if (this.authtoken == null && !header.containsKey(AUTHORIZATION) && header.get(AUTHORIZATION) == null) - throw new IllegalStateException(PLEASE_LOGIN); + if (!isOAuthConfigured() && this.authtoken == null && !header.containsKey(AUTHORIZATION)) { + throw new IllegalStateException("Please login or configure OAuth to access stack"); + } return new Stack(this.instance, header); } From c9e6671d37eeb29e7545db916835f8d003eff558 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 29 Aug 2025 11:03:52 +0530 Subject: [PATCH 135/197] fix: Update OAuth implementation 9 --- .../com/contentstack/cms/Contentstack.java | 23 +++++++++---- .../contentstack/cms/models/OAuthConfig.java | 20 +++++++----- .../contentstack/cms/models/OAuthTokens.java | 13 ++++++++ .../contentstack/cms/oauth/OAuthHandler.java | 31 ++++++++++++++---- .../cms/oauth/OAuthInterceptor.java | 32 +++++++++++++------ 5 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index d65af4d9..5a6b0555 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -295,6 +295,15 @@ public Organization organization() { if (!isOAuthConfigured() && this.authtoken == null) { throw new IllegalStateException("Please login or configure OAuth to access organization"); } + + // If using OAuth, get organization from tokens + if (isOAuthConfigured() && oauthHandler.getTokens() != null) { + String orgUid = oauthHandler.getTokens().getOrganizationUid(); + if (orgUid != null && !orgUid.isEmpty()) { + return organization(orgUid); + } + } + return new Organization(this.instance); } @@ -810,13 +819,15 @@ private OkHttpClient httpClient(Contentstack contentstack, Boolean retryOnFailur // Add either OAuth or traditional auth interceptor if (this.oauthConfig != null) { - if (this.oauthInterceptor == null) { - this.oauthHandler = new OAuthHandler(builder.build(), this.oauthConfig); - this.oauthInterceptor = new OAuthInterceptor(this.oauthHandler); - if (this.earlyAccess != null) { - this.oauthInterceptor.setEarlyAccess(this.earlyAccess); - } + // Create OAuth handler and interceptor first + OkHttpClient tempClient = builder.build(); + this.oauthHandler = new OAuthHandler(tempClient, this.oauthConfig); + this.oauthInterceptor = new OAuthInterceptor(this.oauthHandler); + if (this.earlyAccess != null) { + this.oauthInterceptor.setEarlyAccess(this.earlyAccess); } + + // Add interceptor to final client builder.addInterceptor(this.oauthInterceptor); } else { this.authInterceptor = contentstack.interceptor = new AuthInterceptor(); diff --git a/src/main/java/com/contentstack/cms/models/OAuthConfig.java b/src/main/java/com/contentstack/cms/models/OAuthConfig.java index 107ccaef..247f5285 100644 --- a/src/main/java/com/contentstack/cms/models/OAuthConfig.java +++ b/src/main/java/com/contentstack/cms/models/OAuthConfig.java @@ -64,11 +64,11 @@ public String getFormattedAuthorizationEndpoint() { String hostname = "app.contentstack.com"; - if (hostname.endsWith("io")) { - hostname = hostname.replace("io", "com"); - } - if (hostname.startsWith("api")) { - hostname = hostname.replace("api", "app"); + // Transform hostname if needed + if (hostname.contains("contentstack")) { + hostname = hostname + .replaceAll("^api\\.", "app.") // api.contentstack -> app.contentstack + .replaceAll("\\.io$", ".com"); // *.io -> *.com } return "https://" + hostname + "/#!/apps/" + appId + "/authorize"; @@ -84,9 +84,13 @@ public String getTokenEndpoint() { } String hostname = "developerhub-api.contentstack.com"; - hostname = hostname - .replaceAll("^dev\\d+", "dev") - .replace("io", "com"); + + // Transform hostname if needed + if (hostname.contains("contentstack")) { + hostname = hostname + .replaceAll("^dev\\d+\\.", "dev.") // dev1.* -> dev.* + .replaceAll("\\.io$", ".com"); // *.io -> *.com + } return "https://" + hostname + "/token"; } diff --git a/src/main/java/com/contentstack/cms/models/OAuthTokens.java b/src/main/java/com/contentstack/cms/models/OAuthTokens.java index 39f8d7fa..6b3d1e19 100644 --- a/src/main/java/com/contentstack/cms/models/OAuthTokens.java +++ b/src/main/java/com/contentstack/cms/models/OAuthTokens.java @@ -35,6 +35,9 @@ public class OAuthTokens { @SerializedName("user_uid") private String userUid; + @SerializedName("stack_api_key") + private String stackApiKey; + private Date issuedAt; private Date expiresAt; @@ -147,11 +150,20 @@ public OAuthTokens copy() { copy.scope = this.scope; copy.organizationUid = this.organizationUid; copy.userUid = this.userUid; + copy.stackApiKey = this.stackApiKey; copy.issuedAt = this.issuedAt != null ? new Date(this.issuedAt.getTime()) : null; copy.expiresAt = this.expiresAt != null ? new Date(this.expiresAt.getTime()) : null; return copy; } + /** + * Gets the stack API key if available + * @return The stack API key or null if not available + */ + public String getStackApiKey() { + return stackApiKey != null && !stackApiKey.trim().isEmpty() ? stackApiKey : null; + } + @Override public String toString() { return "OAuthTokens{" + @@ -162,6 +174,7 @@ public String toString() { ", scope='" + scope + '\'' + ", organizationUid='" + organizationUid + '\'' + ", userUid='" + userUid + '\'' + + ", stackApiKey='" + stackApiKey + '\'' + ", issuedAt=" + issuedAt + ", expiresAt=" + expiresAt + '}'; diff --git a/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java b/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java index 93e385f7..0a06bd1a 100644 --- a/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java +++ b/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java @@ -61,9 +61,14 @@ public OAuthHandler(OkHttpClient httpClient, OAuthConfig config) { private Request.Builder _getHeaders() { - return new Request.Builder() - .header("Content-Type", "application/x-www-form-urlencoded") - .header("authorization", "Bearer " + (tokens != null ? tokens.getAccessToken() : "")); + Request.Builder builder = new Request.Builder() + .header("Content-Type", "application/x-www-form-urlencoded"); + + // Only add authorization header for non-token endpoints + if (tokens != null && tokens.getAccessToken() != null) { + builder.header("authorization", "Bearer " + tokens.getAccessToken()); + } + return builder; } /** @@ -194,8 +199,11 @@ public CompletableFuture refreshAccessToken() { .add("refresh_token", tokens.getRefreshToken()) .add("client_id", config.getClientId()); - if (!config.isPkceEnabled()) { + // Add client_secret if available, otherwise add code_verifier + if (config.getClientSecret() != null && !config.getClientSecret().trim().isEmpty()) { formBuilder.add("client_secret", config.getClientSecret()); + } else if (this.codeVerifier != null) { + formBuilder.add("code_verifier", this.codeVerifier); } Request request = _getHeaders() @@ -233,10 +241,13 @@ private OAuthTokens executeTokenRequest(Request request) throws IOException { String error = responseBody != null ? responseBody.string() : "Unknown error"; System.err.println("Error Response Body: " + error); - // Parse error response if possible + // Try to parse error as JSON for better error message try { - throw new RuntimeException("Token request failed: " + error); + com.contentstack.cms.models.Error errorObj = gson.fromJson(error, com.contentstack.cms.models.Error.class); + throw new RuntimeException("Token request failed: " + + (errorObj != null ? errorObj.getErrorMessage() : error)); } catch (JsonParseException e) { + // If not JSON, use raw error string throw new RuntimeException("Token request failed with status " + response.code() + ": " + error); } @@ -364,4 +375,12 @@ public CompletableFuture revokeOauthAppAuthorization() { public String getOrganizationUID() { return tokens != null ? tokens.getOrganizationUid() : null; } public String getUserUID() { return tokens != null ? tokens.getUserUid() : null; } public Long getTokenExpiryTime() { return tokens != null ? tokens.getExpiresIn() : null; } + + /** + * Checks if we have a valid access token + * @return true if we have a non-expired access token + */ + public boolean hasValidAccessToken() { + return tokens != null && tokens.hasAccessToken() && !tokens.isExpired(); + } } \ No newline at end of file diff --git a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java index c7cc6488..18c4c4d6 100644 --- a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java +++ b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java @@ -41,21 +41,35 @@ public boolean hasValidTokens() { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); + + System.out.println("\nOAuth Interceptor - Request Details:"); + System.out.println("URL: " + originalRequest.url()); + System.out.println("Method: " + originalRequest.method()); + System.out.println("Has Tokens: " + (oauthHandler.getTokens() != null)); + if (oauthHandler.getTokens() != null) { + System.out.println("Access Token: " + oauthHandler.getTokens().getAccessToken()); + System.out.println("Token Expired: " + oauthHandler.getTokens().isExpired()); + } + Request.Builder requestBuilder = originalRequest.newBuilder() .header("X-User-Agent", Util.defaultUserAgent()) .header("User-Agent", Util.defaultUserAgent()) .header("Content-Type", originalRequest.url().toString().contains("/token") ? "application/x-www-form-urlencoded" : "application/json") .header("X-Header-EA", earlyAccess != null ? String.join(",", earlyAccess) : "true"); - if (oauthHandler.getTokens() != null) { - if (oauthHandler.getTokens().isExpired() && oauthHandler.getTokens().hasRefreshToken()) { - try { - oauthHandler.refreshAccessToken().get(30, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new IOException("Failed to refresh access token", e); + // Skip auth header for token endpoints + if (!originalRequest.url().toString().contains("/token")) { + if (oauthHandler.getTokens() != null) { + if (oauthHandler.getTokens().isExpired() && oauthHandler.getTokens().hasRefreshToken()) { + try { + oauthHandler.refreshAccessToken().get(30, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new IOException("Failed to refresh access token", e); + } + } + if (oauthHandler.getTokens().hasAccessToken()) { + requestBuilder.header("Authorization", "Bearer " + oauthHandler.getAccessToken()); + System.out.println("Added Authorization header: Bearer " + oauthHandler.getAccessToken()); } - } - if (oauthHandler.getTokens().hasAccessToken()) { - requestBuilder.header("Authorization", "Bearer " + oauthHandler.getAccessToken()); } } From 2fe05208a08ea56f7b9bb2477587894ac4155326 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 29 Aug 2025 12:09:47 +0530 Subject: [PATCH 136/197] fix: Refactor OAuth handling and improve token management --- .../com/contentstack/cms/Contentstack.java | 12 +- .../contentstack/cms/models/OAuthTokens.java | 38 +++++- .../contentstack/cms/oauth/OAuthHandler.java | 93 ++++++++++++--- .../cms/oauth/OAuthInterceptor.java | 108 ++++++++++++++---- 4 files changed, 204 insertions(+), 47 deletions(-) diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 5a6b0555..3b6e2e69 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -801,11 +801,9 @@ private void validateClient(Contentstack contentstack) { // Initialize OAuth if configured if (this.oauthConfig != null) { - this.oauthHandler = contentstack.oauthHandler = new OAuthHandler(httpClient(contentstack, this.retry), this.oauthConfig); - this.oauthInterceptor = contentstack.oauthInterceptor = new OAuthInterceptor(this.oauthHandler); - if (this.earlyAccess != null) { - this.oauthInterceptor.setEarlyAccess(this.earlyAccess); - } + // OAuth handler and interceptor are created in httpClient + contentstack.oauthHandler = this.oauthHandler; + contentstack.oauthInterceptor = this.oauthInterceptor; } } @@ -823,11 +821,13 @@ private OkHttpClient httpClient(Contentstack contentstack, Boolean retryOnFailur OkHttpClient tempClient = builder.build(); this.oauthHandler = new OAuthHandler(tempClient, this.oauthConfig); this.oauthInterceptor = new OAuthInterceptor(this.oauthHandler); + + // Configure early access if needed if (this.earlyAccess != null) { this.oauthInterceptor.setEarlyAccess(this.earlyAccess); } - // Add interceptor to final client + // Add interceptor to handle OAuth, token refresh, and retries builder.addInterceptor(this.oauthInterceptor); } else { this.authInterceptor = contentstack.interceptor = new AuthInterceptor(); diff --git a/src/main/java/com/contentstack/cms/models/OAuthTokens.java b/src/main/java/com/contentstack/cms/models/OAuthTokens.java index 6b3d1e19..d46e07a9 100644 --- a/src/main/java/com/contentstack/cms/models/OAuthTokens.java +++ b/src/main/java/com/contentstack/cms/models/OAuthTokens.java @@ -14,6 +14,7 @@ @Getter @Setter public class OAuthTokens { + private static final String BEARER_TOKEN_TYPE = "Bearer"; @SerializedName("access_token") private String accessToken; @@ -54,10 +55,25 @@ public OAuthTokens() { public void setExpiresIn(Long expiresIn) { this.expiresIn = expiresIn; if (expiresIn != null) { - this.expiresAt = new Date(issuedAt.getTime() + (expiresIn * 1000)); + setExpiresAt(new Date(System.currentTimeMillis() + (expiresIn * 1000))); } } + public synchronized void setExpiresAt(Date expiresAt) { + this.expiresAt = expiresAt != null ? new Date(expiresAt.getTime()) : null; + if (expiresAt != null) { + this.expiresIn = (expiresAt.getTime() - System.currentTimeMillis()) / 1000; + } + } + + public synchronized Date getExpiresAt() { + return expiresAt != null ? new Date(expiresAt.getTime()) : null; + } + + public synchronized Date getIssuedAt() { + return issuedAt != null ? new Date(issuedAt.getTime()) : null; + } + /** * Gets the scopes as a list * @return List of scope strings or empty list if no scopes @@ -95,18 +111,32 @@ public boolean hasScope(String scopeToCheck) { * @return true if token is expired or will expire soon */ public boolean isExpired() { + // No expiry time means token is considered expired if (expiresAt == null) { return true; } - return System.currentTimeMillis() + EXPIRY_BUFFER_MS > expiresAt.getTime(); + + // No access token means token is considered expired + if (!hasAccessToken()) { + return true; + } + + // Check if current time + buffer is past expiry + long currentTime = System.currentTimeMillis(); + long expiryTime = expiresAt.getTime(); + long timeUntilExpiry = expiryTime - currentTime; + + // Consider expired if within buffer window + return timeUntilExpiry <= EXPIRY_BUFFER_MS; } /** * Checks if the token is valid (has access token and not expired) * @return true if token is valid */ - public boolean isValid() { - return hasAccessToken() && !isExpired(); + public synchronized boolean isValid() { + return hasAccessToken() && !isExpired() && + BEARER_TOKEN_TYPE.equalsIgnoreCase(tokenType); } /** diff --git a/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java b/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java index 0a06bd1a..9cba329d 100644 --- a/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java +++ b/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java @@ -7,6 +7,7 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Base64; +import java.util.Date; import java.util.concurrent.CompletableFuture; import com.contentstack.cms.models.OAuthConfig; @@ -31,6 +32,7 @@ public class OAuthHandler { private OkHttpClient httpClient; private final OAuthConfig config; private final Gson gson; + private final Object tokenLock = new Object(); private String codeVerifier; private String codeChallenge; @@ -118,7 +120,8 @@ public String authorize() { StringBuilder urlBuilder = new StringBuilder(baseUrl); urlBuilder.append("?response_type=").append(config.getResponseType()) .append("&client_id=").append(URLEncoder.encode(config.getClientId(), "UTF-8")) - .append("&redirect_uri=").append(URLEncoder.encode(config.getRedirectUri(), "UTF-8")); + .append("&redirect_uri=").append(URLEncoder.encode(config.getRedirectUri(), "UTF-8")) + .append("&app_id=").append(URLEncoder.encode(config.getAppId(), "UTF-8")); // Add scope if provided if (config.getScope() != null && !config.getScope().trim().isEmpty()) { @@ -145,11 +148,16 @@ public String authorize() { * @return Future containing the tokens */ public CompletableFuture exchangeCodeForToken(String code) { + if (code == null || code.trim().isEmpty()) { + return CompletableFuture.failedFuture(new IllegalArgumentException("Authorization code cannot be null or empty")); + } + + System.out.println("\nExchanging authorization code for tokens..."); return CompletableFuture.supplyAsync(() -> { try { FormBody.Builder formBuilder = new FormBody.Builder() .add("grant_type", "authorization_code") - .add("code", code) + .add("code", code.trim()) .add("redirect_uri", config.getRedirectUri()) .add("client_id", config.getClientId()) .add("app_id", config.getAppId()); @@ -178,7 +186,15 @@ public CompletableFuture exchangeCodeForToken(String code) { * @param tokens The tokens to save */ private void _saveTokens(OAuthTokens tokens) { - this.tokens = tokens; + synchronized (tokenLock) { + this.tokens = tokens; + } + } + + private OAuthTokens _getTokens() { + synchronized (tokenLock) { + return this.tokens; + } } /** @@ -186,18 +202,33 @@ private void _saveTokens(OAuthTokens tokens) { * @return Future containing the new tokens */ public CompletableFuture refreshAccessToken() { - if (tokens == null || !tokens.hasRefreshToken()) { + // Check if we have tokens and refresh token + if (tokens == null) { + return CompletableFuture.failedFuture( + new IllegalStateException("No tokens available")); + } + if (!tokens.hasRefreshToken()) { return CompletableFuture.failedFuture( new IllegalStateException("No refresh token available")); } + + // Check if token is actually expired + if (!tokens.isExpired()) { + return CompletableFuture.completedFuture(tokens); + } return CompletableFuture.supplyAsync(() -> { try { + System.out.println("\nRefreshing access token..."); + System.out.println("Current token expired: " + tokens.isExpired()); + System.out.println("Has refresh token: " + tokens.hasRefreshToken()); + System.out.println("Time until expiry: " + tokens.getTimeUntilExpiration() + "ms"); + FormBody.Builder formBuilder = new FormBody.Builder() - .add("app_id", config.getAppId()) .add("grant_type", "refresh_token") .add("refresh_token", tokens.getRefreshToken()) - .add("client_id", config.getClientId()); + .add("client_id", config.getClientId()) + .add("app_id", config.getAppId()); // Add client_secret if available, otherwise add code_verifier if (config.getClientSecret() != null && !config.getClientSecret().trim().isEmpty()) { @@ -206,13 +237,18 @@ public CompletableFuture refreshAccessToken() { formBuilder.add("code_verifier", this.codeVerifier); } - Request request = _getHeaders() + Request request = new Request.Builder() .url(config.getTokenEndpoint()) + .header("Content-Type", "application/x-www-form-urlencoded") .post(formBuilder.build()) .build(); - return executeTokenRequest(request); + OAuthTokens newTokens = executeTokenRequest(request); + System.out.println("Token refresh successful!"); + System.out.println("New token expires in: " + newTokens.getExpiresIn() + " seconds"); + return newTokens; } catch (IOException | RuntimeException e) { + System.err.println("Token refresh failed: " + e.getMessage()); throw new RuntimeException("Failed to refresh tokens", e); } }); @@ -258,8 +294,13 @@ private OAuthTokens executeTokenRequest(Request request) throws IOException { OAuthTokens newTokens = gson.fromJson(body, OAuthTokens.class); - // Keep old refresh token if new one not provided - if (this.tokens != null && newTokens.getRefreshToken() == null) { + // Set token expiry time + if (newTokens.getExpiresIn() != null) { + newTokens.setExpiresAt(new Date(System.currentTimeMillis() + (newTokens.getExpiresIn() * 1000))); + } + + // Keep refresh token if new one not provided + if (newTokens.getRefreshToken() == null && this.tokens != null && this.tokens.hasRefreshToken()) { newTokens.setRefreshToken(this.tokens.getRefreshToken()); } @@ -370,17 +411,37 @@ public CompletableFuture revokeOauthAppAuthorization() { } // Convenience methods for token access - public String getAccessToken() { return tokens != null ? tokens.getAccessToken() : null; } - public String getRefreshToken() { return tokens != null ? tokens.getRefreshToken() : null; } - public String getOrganizationUID() { return tokens != null ? tokens.getOrganizationUid() : null; } - public String getUserUID() { return tokens != null ? tokens.getUserUid() : null; } - public Long getTokenExpiryTime() { return tokens != null ? tokens.getExpiresIn() : null; } + public String getAccessToken() { + OAuthTokens t = _getTokens(); + return t != null ? t.getAccessToken() : null; + } + + public String getRefreshToken() { + OAuthTokens t = _getTokens(); + return t != null ? t.getRefreshToken() : null; + } + + public String getOrganizationUID() { + OAuthTokens t = _getTokens(); + return t != null ? t.getOrganizationUid() : null; + } + + public String getUserUID() { + OAuthTokens t = _getTokens(); + return t != null ? t.getUserUid() : null; + } + + public Long getTokenExpiryTime() { + OAuthTokens t = _getTokens(); + return t != null ? t.getExpiresIn() : null; + } /** * Checks if we have a valid access token * @return true if we have a non-expired access token */ public boolean hasValidAccessToken() { - return tokens != null && tokens.hasAccessToken() && !tokens.isExpired(); + OAuthTokens t = _getTokens(); + return t != null && t.hasAccessToken() && !t.isExpired(); } } \ No newline at end of file diff --git a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java index 18c4c4d6..1fe1c110 100644 --- a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java +++ b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java @@ -14,8 +14,10 @@ public class OAuthInterceptor implements Interceptor { private static final Logger logger = Logger.getLogger(OAuthInterceptor.class.getName()); + private static final int MAX_RETRIES = 3; private final OAuthHandler oauthHandler; private String[] earlyAccess; + private final Object refreshLock = new Object(); public OAuthInterceptor(OAuthHandler oauthHandler) { this.oauthHandler = oauthHandler; @@ -58,33 +60,97 @@ public Response intercept(Chain chain) throws IOException { .header("X-Header-EA", earlyAccess != null ? String.join(",", earlyAccess) : "true"); // Skip auth header for token endpoints if (!originalRequest.url().toString().contains("/token")) { - if (oauthHandler.getTokens() != null) { - if (oauthHandler.getTokens().isExpired() && oauthHandler.getTokens().hasRefreshToken()) { - try { - oauthHandler.refreshAccessToken().get(30, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new IOException("Failed to refresh access token", e); - } - } - if (oauthHandler.getTokens().hasAccessToken()) { - requestBuilder.header("Authorization", "Bearer " + oauthHandler.getAccessToken()); - System.out.println("Added Authorization header: Bearer " + oauthHandler.getAccessToken()); + if (oauthHandler.getTokens() != null && oauthHandler.getTokens().hasAccessToken()) { + requestBuilder.header("Authorization", "Bearer " + oauthHandler.getAccessToken()); + System.out.println("Added Authorization header: Bearer " + oauthHandler.getAccessToken()); + } + } + + // Execute request with retry and refresh handling + return executeRequest(chain, requestBuilder.build(), 0); + } + + private Response executeRequest(Chain chain, Request request, int retryCount) throws IOException { + // Skip token refresh for token endpoints to avoid infinite loops + if (request.url().toString().contains("/token")) { + return chain.proceed(request); + } + + // Ensure we have tokens + if (oauthHandler == null || oauthHandler.getTokens() == null) { + throw new IOException("No OAuth tokens available. Please authenticate first."); + } + + // Check if we need to refresh the token before making the request + if (oauthHandler.getTokens().isExpired()) { + + synchronized (refreshLock) { + try { + logger.info("Token expired, refreshing..."); + oauthHandler.refreshAccessToken().get(30, TimeUnit.SECONDS); + + // Update authorization header with new token + request = request.newBuilder() + .header("Authorization", "Bearer " + oauthHandler.getAccessToken()) + .build(); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + logger.severe("Token refresh failed: " + e.getMessage()); + throw new IOException("Failed to refresh access token", e); } } } - Request request = requestBuilder.build(); + // Execute request Response response = chain.proceed(request); - if (response.code() == 401 && oauthHandler.getTokens() != null && oauthHandler.getTokens().hasRefreshToken()) { - response.close(); + + // Handle error responses + if (!response.isSuccessful() && retryCount < MAX_RETRIES) { + int code = response.code(); + String body = null; try { - oauthHandler.refreshAccessToken().get(30, TimeUnit.SECONDS); - request = request.newBuilder() - .header("Authorization", "Bearer " + oauthHandler.getAccessToken()) - .build(); - return chain.proceed(request); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new IOException("Failed to refresh access token after 401", e); + if (response.body() != null) { + body = response.body().string(); + } + } catch (IOException e) { + // Ignore body read errors + } + response.close(); + + logger.info("Request failed with code " + code + ": " + body); + + // Handle 401 with token refresh + if (code == 401 && oauthHandler != null && oauthHandler.getTokens() != null && + oauthHandler.getTokens().hasRefreshToken()) { + + synchronized (refreshLock) { + try { + logger.info("Attempting token refresh after 401"); + oauthHandler.refreshAccessToken().get(30, TimeUnit.SECONDS); + + // Update authorization header with new token + request = request.newBuilder() + .header("Authorization", "Bearer " + oauthHandler.getAccessToken()) + .build(); + + return executeRequest(chain, request, retryCount + 1); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new IOException("Failed to refresh access token after 401", e); + } + } + } + + // Handle other retryable errors (429, 5xx) + if ((code == 429 || code >= 500) && code != 501) { + try { + // Exponential backoff + long delay = Math.min(1000 * (1 << retryCount), 30000); + logger.info("Retrying request after " + delay + "ms delay"); + Thread.sleep(delay); + return executeRequest(chain, request, retryCount + 1); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Retry interrupted", e); + } } } From cc1002087bdee269fb0c5682513a16def58730cf Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 3 Sep 2025 16:42:25 +0530 Subject: [PATCH 137/197] chore: Release version 1.8.0 with OAuth 2.0 support and code improvements --- changelog.md | 7 + pom.xml | 2 +- .../contentstack/cms/models/OAuthConfig.java | 32 +- .../contentstack/cms/models/OAuthTokens.java | 74 ++- .../contentstack/cms/oauth/OAuthHandler.java | 206 ++++---- .../cms/oauth/OAuthInterceptor.java | 86 ++-- .../com/contentstack/cms/oauth/OAuthTest.java | 438 ++++++++++++++++++ 7 files changed, 624 insertions(+), 221 deletions(-) create mode 100644 src/test/java/com/contentstack/cms/oauth/OAuthTest.java diff --git a/changelog.md b/changelog.md index 3212b721..222ab339 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## v1.8.0 + +### Sep 15, 2025 + +- Feature : OAuth 2.0 support with PKCE flow +- Improved code organization and removed redundant methods + ## v1.7.1 ### Jul 21, 2025 diff --git a/pom.xml b/pom.xml index 43ac7c04..f2e0e305 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.7.1-SNAPSHOT + 1.8.0 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach diff --git a/src/main/java/com/contentstack/cms/models/OAuthConfig.java b/src/main/java/com/contentstack/cms/models/OAuthConfig.java index 247f5285..af1b99d9 100644 --- a/src/main/java/com/contentstack/cms/models/OAuthConfig.java +++ b/src/main/java/com/contentstack/cms/models/OAuthConfig.java @@ -14,6 +14,7 @@ @Getter @Builder public class OAuthConfig { + private final String appId; private final String clientId; private final String clientSecret; @@ -25,7 +26,9 @@ public class OAuthConfig { /** * Validates the configuration - * @throws IllegalArgumentException if required fields are missing or invalid + * + * @throws IllegalArgumentException if required fields are missing or + * invalid */ public void validate() { if (appId == null || appId.trim().isEmpty()) { @@ -47,6 +50,7 @@ public void validate() { /** * Checks if PKCE flow should be used (when clientSecret is not provided) + * * @return true if PKCE should be used */ public boolean isPkceEnabled() { @@ -55,6 +59,7 @@ public boolean isPkceEnabled() { /** * Gets the formatted authorization endpoint URL + * * @return The authorization endpoint URL */ public String getFormattedAuthorizationEndpoint() { @@ -63,19 +68,20 @@ public String getFormattedAuthorizationEndpoint() { } String hostname = "app.contentstack.com"; - + // Transform hostname if needed if (hostname.contains("contentstack")) { hostname = hostname - .replaceAll("^api\\.", "app.") // api.contentstack -> app.contentstack - .replaceAll("\\.io$", ".com"); // *.io -> *.com + .replaceAll("^api\\.", "app.") // api.contentstack -> app.contentstack + .replaceAll("\\.io$", ".com"); // *.io -> *.com } - + return "https://" + hostname + "/#!/apps/" + appId + "/authorize"; } /** * Gets the formatted token endpoint URL + * * @return The token endpoint URL */ public String getTokenEndpoint() { @@ -84,19 +90,20 @@ public String getTokenEndpoint() { } String hostname = "developerhub-api.contentstack.com"; - + // Transform hostname if needed if (hostname.contains("contentstack")) { hostname = hostname - .replaceAll("^dev\\d+\\.", "dev.") // dev1.* -> dev.* - .replaceAll("\\.io$", ".com"); // *.io -> *.com + .replaceAll("^dev\\d+\\.", "dev.") // dev1.* -> dev.* + .replaceAll("\\.io$", ".com"); // *.io -> *.com } - + return "https://" + hostname + "/token"; } /** * Gets the response type, defaulting to "code" + * * @return The response type */ public String getResponseType() { @@ -105,6 +112,7 @@ public String getResponseType() { /** * Gets the scopes as a list + * * @return List of scope strings or empty list if no scopes */ public List getScopesList() { @@ -116,6 +124,7 @@ public List getScopesList() { /** * Gets the scope string + * * @return The space-delimited scope string or null */ public String getScope() { @@ -126,8 +135,10 @@ public String getScope() { * Builder class for OAuthConfig */ public static class OAuthConfigBuilder { + /** * Sets scopes from a list + * * @param scopes List of scope strings * @return Builder instance */ @@ -142,6 +153,7 @@ public OAuthConfigBuilder scopes(List scopes) { /** * Sets scopes from varargs + * * @param scopes Scope strings * @return Builder instance */ @@ -154,4 +166,4 @@ public OAuthConfigBuilder scopes(String... scopes) { return this; } } -} \ No newline at end of file +} diff --git a/src/main/java/com/contentstack/cms/models/OAuthTokens.java b/src/main/java/com/contentstack/cms/models/OAuthTokens.java index d46e07a9..3d8e4115 100644 --- a/src/main/java/com/contentstack/cms/models/OAuthTokens.java +++ b/src/main/java/com/contentstack/cms/models/OAuthTokens.java @@ -14,6 +14,7 @@ @Getter @Setter public class OAuthTokens { + private static final String BEARER_TOKEN_TYPE = "Bearer"; @SerializedName("access_token") private String accessToken; @@ -50,6 +51,7 @@ public OAuthTokens() { /** * Sets the expiration time in seconds and calculates the expiry date + * * @param expiresIn Expiration time in seconds */ public void setExpiresIn(Long expiresIn) { @@ -76,6 +78,7 @@ public synchronized Date getIssuedAt() { /** * Gets the scopes as a list + * * @return List of scope strings or empty list if no scopes */ public List getScopesList() { @@ -87,6 +90,7 @@ public List getScopesList() { /** * Sets scopes from a list + * * @param scopes List of scope strings */ public void setScopesList(List scopes) { @@ -99,6 +103,7 @@ public void setScopesList(List scopes) { /** * Checks if the token has a specific scope + * * @param scopeToCheck The scope to check for * @return true if the token has the scope */ @@ -108,6 +113,7 @@ public boolean hasScope(String scopeToCheck) { /** * Checks if the token is expired, including a buffer time + * * @return true if token is expired or will expire soon */ public boolean isExpired() { @@ -115,32 +121,34 @@ public boolean isExpired() { if (expiresAt == null) { return true; } - + // No access token means token is considered expired if (!hasAccessToken()) { return true; } - + // Check if current time + buffer is past expiry long currentTime = System.currentTimeMillis(); long expiryTime = expiresAt.getTime(); long timeUntilExpiry = expiryTime - currentTime; - + // Consider expired if within buffer window return timeUntilExpiry <= EXPIRY_BUFFER_MS; } /** * Checks if the token is valid (has access token and not expired) + * * @return true if token is valid */ public synchronized boolean isValid() { - return hasAccessToken() && !isExpired() && - BEARER_TOKEN_TYPE.equalsIgnoreCase(tokenType); + return hasAccessToken() && !isExpired() + && BEARER_TOKEN_TYPE.equalsIgnoreCase(tokenType); } /** * Checks if access token is present + * * @return true if access token exists */ public boolean hasAccessToken() { @@ -149,6 +157,7 @@ public boolean hasAccessToken() { /** * Checks if refresh token is present + * * @return true if refresh token exists */ public boolean hasRefreshToken() { @@ -157,6 +166,7 @@ public boolean hasRefreshToken() { /** * Gets time until token expiration in milliseconds + * * @return milliseconds until expiration or 0 if expired/invalid */ public long getTimeUntilExpiration() { @@ -167,46 +177,20 @@ public long getTimeUntilExpiration() { return Math.max(0, timeLeft); } - /** - * Creates a copy of this token object - * @return A new OAuthTokens instance with copied values - */ - public OAuthTokens copy() { - OAuthTokens copy = new OAuthTokens(); - copy.accessToken = this.accessToken; - copy.refreshToken = this.refreshToken; - copy.tokenType = this.tokenType; - copy.expiresIn = this.expiresIn; - copy.scope = this.scope; - copy.organizationUid = this.organizationUid; - copy.userUid = this.userUid; - copy.stackApiKey = this.stackApiKey; - copy.issuedAt = this.issuedAt != null ? new Date(this.issuedAt.getTime()) : null; - copy.expiresAt = this.expiresAt != null ? new Date(this.expiresAt.getTime()) : null; - return copy; - } - - /** - * Gets the stack API key if available - * @return The stack API key or null if not available - */ - public String getStackApiKey() { - return stackApiKey != null && !stackApiKey.trim().isEmpty() ? stackApiKey : null; - } @Override public String toString() { - return "OAuthTokens{" + - "accessToken='" + (accessToken != null ? "[REDACTED]" : "null") + '\'' + - ", refreshToken='" + (refreshToken != null ? "[REDACTED]" : "null") + '\'' + - ", tokenType='" + tokenType + '\'' + - ", expiresIn=" + expiresIn + - ", scope='" + scope + '\'' + - ", organizationUid='" + organizationUid + '\'' + - ", userUid='" + userUid + '\'' + - ", stackApiKey='" + stackApiKey + '\'' + - ", issuedAt=" + issuedAt + - ", expiresAt=" + expiresAt + - '}'; - } -} \ No newline at end of file + return "OAuthTokens{" + + "accessToken='" + (accessToken != null ? "[REDACTED]" : "null") + '\'' + + ", refreshToken='" + (refreshToken != null ? "[REDACTED]" : "null") + '\'' + + ", tokenType='" + tokenType + '\'' + + ", expiresIn=" + expiresIn + + ", scope='" + scope + '\'' + + ", organizationUid='" + organizationUid + '\'' + + ", userUid='" + userUid + '\'' + + ", stackApiKey='" + stackApiKey + '\'' + + ", issuedAt=" + issuedAt + + ", expiresAt=" + expiresAt + + '}'; + } +} diff --git a/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java b/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java index 9cba329d..e969ebbc 100644 --- a/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java +++ b/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java @@ -24,11 +24,12 @@ import okhttp3.ResponseBody; /** - * Handles OAuth 2.0 authentication flow for Contentstack - * Supports both traditional OAuth flow with client secret and PKCE flow + * Handles OAuth 2.0 authentication flow for Contentstack Supports both + * traditional OAuth flow with client secret and PKCE flow */ @Getter public class OAuthHandler { + private OkHttpClient httpClient; private final OAuthConfig config; private final Gson gson; @@ -37,12 +38,14 @@ public class OAuthHandler { private String codeVerifier; private String codeChallenge; private String state; - - @Getter @Setter + + @Getter + @Setter private OAuthTokens tokens; /** * Creates a new OAuth handler instance + * * @param httpClient HTTP client for making requests * @param config OAuth configuration */ @@ -53,7 +56,7 @@ public OAuthHandler(OkHttpClient httpClient, OAuthConfig config) { // Validate config before proceeding config.validate(); - + // Only generate PKCE codeVerifier if clientSecret is not provided if (config.getClientSecret() == null || config.getClientSecret().trim().isEmpty()) { this.codeVerifier = generateCodeVerifier(); @@ -61,11 +64,10 @@ public OAuthHandler(OkHttpClient httpClient, OAuthConfig config) { } } - private Request.Builder _getHeaders() { Request.Builder builder = new Request.Builder() - .header("Content-Type", "application/x-www-form-urlencoded"); - + .header("Content-Type", "application/x-www-form-urlencoded"); + // Only add authorization header for non-token endpoints if (tokens != null && tokens.getAccessToken() != null) { builder.header("authorization", "Bearer " + tokens.getAccessToken()); @@ -75,10 +77,11 @@ private Request.Builder _getHeaders() { /** * Generates a cryptographically secure code verifier for PKCE + * * @return A random URL-safe string between 43-128 characters */ private String generateCodeVerifier() { - final int CODE_VERIFIER_LENGTH = 96; + final int CODE_VERIFIER_LENGTH = 96; final String charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"; SecureRandom random = new SecureRandom(); StringBuilder verifier = new StringBuilder(); @@ -90,6 +93,7 @@ private String generateCodeVerifier() { /** * Generates code challenge from code verifier using SHA-256 + * * @param verifier The code verifier to hash * @return BASE64URL-encoded SHA256 hash of the verifier */ @@ -97,7 +101,7 @@ private String generateCodeChallenge(String verifier) { try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] hash = digest.digest(verifier.getBytes(StandardCharsets.UTF_8)); - + String base64String = Base64.getEncoder().encodeToString(hash); return base64String .replace('+', '-') @@ -108,9 +112,9 @@ private String generateCodeChallenge(String verifier) { } } - /** * Starts the OAuth authorization flow + * * @return Authorization URL for the user to visit */ public String authorize() { @@ -119,10 +123,10 @@ public String authorize() { StringBuilder urlBuilder = new StringBuilder(baseUrl); urlBuilder.append("?response_type=").append(config.getResponseType()) - .append("&client_id=").append(URLEncoder.encode(config.getClientId(), "UTF-8")) - .append("&redirect_uri=").append(URLEncoder.encode(config.getRedirectUri(), "UTF-8")) - .append("&app_id=").append(URLEncoder.encode(config.getAppId(), "UTF-8")); - + .append("&client_id=").append(URLEncoder.encode(config.getClientId(), "UTF-8")) + .append("&redirect_uri=").append(URLEncoder.encode(config.getRedirectUri(), "UTF-8")) + .append("&app_id=").append(URLEncoder.encode(config.getAppId(), "UTF-8")); + // Add scope if provided if (config.getScope() != null && !config.getScope().trim().isEmpty()) { urlBuilder.append("&scope=").append(URLEncoder.encode(config.getScope(), "UTF-8")); @@ -134,7 +138,7 @@ public String authorize() { // PKCE flow: add code_challenge this.codeChallenge = generateCodeChallenge(this.codeVerifier); urlBuilder.append("&code_challenge=").append(URLEncoder.encode(this.codeChallenge, "UTF-8")) - .append("&code_challenge_method=S256"); + .append("&code_challenge_method=S256"); return urlBuilder.toString(); } } catch (IOException e) { @@ -144,6 +148,7 @@ public String authorize() { /** * Exchanges authorization code for tokens + * * @param code The authorization code from callback * @return Future containing the tokens */ @@ -151,16 +156,14 @@ public CompletableFuture exchangeCodeForToken(String code) { if (code == null || code.trim().isEmpty()) { return CompletableFuture.failedFuture(new IllegalArgumentException("Authorization code cannot be null or empty")); } - - System.out.println("\nExchanging authorization code for tokens..."); return CompletableFuture.supplyAsync(() -> { try { FormBody.Builder formBuilder = new FormBody.Builder() - .add("grant_type", "authorization_code") - .add("code", code.trim()) - .add("redirect_uri", config.getRedirectUri()) - .add("client_id", config.getClientId()) - .add("app_id", config.getAppId()); + .add("grant_type", "authorization_code") + .add("code", code.trim()) + .add("redirect_uri", config.getRedirectUri()) + .add("client_id", config.getClientId()) + .add("app_id", config.getAppId()); // Choose between client_secret and code_verifier like JS SDK if (config.getClientSecret() != null) { @@ -170,9 +173,9 @@ public CompletableFuture exchangeCodeForToken(String code) { } Request request = _getHeaders() - .url(config.getTokenEndpoint()) - .post(formBuilder.build()) - .build(); + .url(config.getTokenEndpoint()) + .post(formBuilder.build()) + .build(); return executeTokenRequest(request); } catch (IOException | RuntimeException e) { @@ -183,6 +186,7 @@ public CompletableFuture exchangeCodeForToken(String code) { /** * Saves tokens from a successful OAuth response + * * @param tokens The tokens to save */ private void _saveTokens(OAuthTokens tokens) { @@ -199,19 +203,20 @@ private OAuthTokens _getTokens() { /** * Refreshes the access token using the refresh token + * * @return Future containing the new tokens */ public CompletableFuture refreshAccessToken() { // Check if we have tokens and refresh token if (tokens == null) { return CompletableFuture.failedFuture( - new IllegalStateException("No tokens available")); + new IllegalStateException("No tokens available")); } if (!tokens.hasRefreshToken()) { return CompletableFuture.failedFuture( - new IllegalStateException("No refresh token available")); + new IllegalStateException("No refresh token available")); } - + // Check if token is actually expired if (!tokens.isExpired()) { return CompletableFuture.completedFuture(tokens); @@ -219,16 +224,12 @@ public CompletableFuture refreshAccessToken() { return CompletableFuture.supplyAsync(() -> { try { - System.out.println("\nRefreshing access token..."); - System.out.println("Current token expired: " + tokens.isExpired()); - System.out.println("Has refresh token: " + tokens.hasRefreshToken()); - System.out.println("Time until expiry: " + tokens.getTimeUntilExpiration() + "ms"); FormBody.Builder formBuilder = new FormBody.Builder() - .add("grant_type", "refresh_token") - .add("refresh_token", tokens.getRefreshToken()) - .add("client_id", config.getClientId()) - .add("app_id", config.getAppId()); + .add("grant_type", "refresh_token") + .add("refresh_token", tokens.getRefreshToken()) + .add("client_id", config.getClientId()) + .add("app_id", config.getAppId()); // Add client_secret if available, otherwise add code_verifier if (config.getClientSecret() != null && !config.getClientSecret().trim().isEmpty()) { @@ -238,17 +239,16 @@ public CompletableFuture refreshAccessToken() { } Request request = new Request.Builder() - .url(config.getTokenEndpoint()) - .header("Content-Type", "application/x-www-form-urlencoded") - .post(formBuilder.build()) - .build(); + .url(config.getTokenEndpoint()) + .header("Content-Type", "application/x-www-form-urlencoded") + .post(formBuilder.build()) + .build(); OAuthTokens newTokens = executeTokenRequest(request); - System.out.println("Token refresh successful!"); - System.out.println("New token expires in: " + newTokens.getExpiresIn() + " seconds"); + return newTokens; } catch (IOException | RuntimeException e) { - System.err.println("Token refresh failed: " + e.getMessage()); + throw new RuntimeException("Failed to refresh tokens", e); } }); @@ -258,10 +258,6 @@ public CompletableFuture refreshAccessToken() { * Executes a token request and processes the response */ private OAuthTokens executeTokenRequest(Request request) throws IOException { - System.out.println("\nToken Request Details:"); - System.out.println("URL: " + request.url()); - System.out.println("Method: " + request.method()); - System.out.println("Headers: " + request.headers()); Response response = null; ResponseBody responseBody = null; @@ -269,41 +265,35 @@ private OAuthTokens executeTokenRequest(Request request) throws IOException { response = httpClient.newCall(request).execute(); responseBody = response.body(); - System.out.println("\nToken Response Details:"); - System.out.println("Status Code: " + response.code()); - System.out.println("Headers: " + response.headers()); - if (!response.isSuccessful()) { String error = responseBody != null ? responseBody.string() : "Unknown error"; - System.err.println("Error Response Body: " + error); - + // Try to parse error as JSON for better error message try { com.contentstack.cms.models.Error errorObj = gson.fromJson(error, com.contentstack.cms.models.Error.class); - throw new RuntimeException("Token request failed: " + - (errorObj != null ? errorObj.getErrorMessage() : error)); + throw new RuntimeException("Token request failed: " + + (errorObj != null ? errorObj.getErrorMessage() : error)); } catch (JsonParseException e) { // If not JSON, use raw error string - throw new RuntimeException("Token request failed with status " + - response.code() + ": " + error); + throw new RuntimeException("Token request failed with status " + + response.code() + ": " + error); } } String body = responseBody != null ? responseBody.string() : "{}"; - System.out.println("Success Response Body: " + body); - + OAuthTokens newTokens = gson.fromJson(body, OAuthTokens.class); - + // Set token expiry time if (newTokens.getExpiresIn() != null) { newTokens.setExpiresAt(new Date(System.currentTimeMillis() + (newTokens.getExpiresIn() * 1000))); } - + // Keep refresh token if new one not provided if (newTokens.getRefreshToken() == null && this.tokens != null && this.tokens.hasRefreshToken()) { newTokens.setRefreshToken(this.tokens.getRefreshToken()); } - + _saveTokens(newTokens); return newTokens; } catch (JsonParseException e) { @@ -318,17 +308,9 @@ private OAuthTokens executeTokenRequest(Request request) throws IOException { } } - /** - * Handles the OAuth redirect by exchanging the code for tokens - * @param code Authorization code from the redirect - * @return Future containing the tokens - */ - public CompletableFuture handleRedirect(String code) { - return exchangeCodeForToken(code); - } - /** * Logs out the user and optionally revokes authorization + * * @param revokeAuthorization Whether to revoke the app authorization */ public CompletableFuture logout(boolean revokeAuthorization) { @@ -342,15 +324,16 @@ public CompletableFuture logout(boolean revokeAuthorization) { /** * Gets the current OAuth app authorization status + * * @return Future containing the authorization details */ public CompletableFuture getOauthAppAuthorization() { return CompletableFuture.supplyAsync(() -> { try { Request request = _getHeaders() - .url(config.getFormattedAuthorizationEndpoint() + "/status") - .get() - .build(); + .url(config.getFormattedAuthorizationEndpoint() + "/status") + .get() + .build(); Response response = null; ResponseBody responseBody = null; @@ -366,8 +349,12 @@ public CompletableFuture getOauthAppAuthorization() { String body = responseBody != null ? responseBody.string() : "{}"; return gson.fromJson(body, OAuthTokens.class); } finally { - if (responseBody != null) responseBody.close(); - if (response != null) response.close(); + if (responseBody != null) { + responseBody.close(); + } + if (response != null) { + response.close(); + } } } catch (IOException | RuntimeException e) { throw new RuntimeException("Failed to get authorization status", e); @@ -377,18 +364,19 @@ public CompletableFuture getOauthAppAuthorization() { /** * Revokes the OAuth app authorization + * * @return Future that completes when revocation is done */ public CompletableFuture revokeOauthAppAuthorization() { return CompletableFuture.runAsync(() -> { try { Request request = _getHeaders() - .url(config.getFormattedAuthorizationEndpoint() + "/revoke") - .post(new FormBody.Builder() - .add("app_id", config.getAppId()) - .add("client_id", config.getClientId()) - .build()) - .build(); + .url(config.getFormattedAuthorizationEndpoint() + "/revoke") + .post(new FormBody.Builder() + .add("app_id", config.getAppId()) + .add("client_id", config.getClientId()) + .build()) + .build(); Response response = null; ResponseBody responseBody = null; @@ -401,8 +389,12 @@ public CompletableFuture revokeOauthAppAuthorization() { throw new RuntimeException("Failed to revoke authorization: " + error); } } finally { - if (responseBody != null) responseBody.close(); - if (response != null) response.close(); + if (responseBody != null) { + responseBody.close(); + } + if (response != null) { + response.close(); + } } } catch (IOException | RuntimeException e) { throw new RuntimeException("Failed to revoke authorization", e); @@ -411,37 +403,33 @@ public CompletableFuture revokeOauthAppAuthorization() { } // Convenience methods for token access - public String getAccessToken() { - OAuthTokens t = _getTokens(); - return t != null ? t.getAccessToken() : null; - } - - public String getRefreshToken() { - OAuthTokens t = _getTokens(); - return t != null ? t.getRefreshToken() : null; + public String getAccessToken() { + OAuthTokens accessToken = _getTokens(); + return accessToken != null ? accessToken.getAccessToken() : null; } - - public String getOrganizationUID() { - OAuthTokens t = _getTokens(); - return t != null ? t.getOrganizationUid() : null; + + public String getRefreshToken() { + OAuthTokens refreshToken = _getTokens(); + return refreshToken != null ? refreshToken.getRefreshToken() : null; } - - public String getUserUID() { - OAuthTokens t = _getTokens(); - return t != null ? t.getUserUid() : null; + + public String getOrganizationUID() { + OAuthTokens organizationUid = _getTokens(); + return organizationUid != null ? organizationUid.getOrganizationUid() : null; } - - public Long getTokenExpiryTime() { - OAuthTokens t = _getTokens(); - return t != null ? t.getExpiresIn() : null; + + public String getUserUID() { + OAuthTokens userUid = _getTokens(); + return userUid != null ? userUid.getUserUid() : null; } - + /** * Checks if we have a valid access token + * * @return true if we have a non-expired access token */ public boolean hasValidAccessToken() { - OAuthTokens t = _getTokens(); - return t != null && t.hasAccessToken() && !t.isExpired(); + OAuthTokens accessToken = _getTokens(); + return accessToken != null && accessToken.hasAccessToken() && !accessToken.isExpired(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java index 1fe1c110..df520748 100644 --- a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java +++ b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java @@ -1,19 +1,18 @@ package com.contentstack.cms.oauth; -import com.contentstack.cms.core.Util; -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - import java.io.IOException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.logging.Logger; +import com.contentstack.cms.core.Util; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; public class OAuthInterceptor implements Interceptor { - private static final Logger logger = Logger.getLogger(OAuthInterceptor.class.getName()); + private static final int MAX_RETRIES = 3; private final OAuthHandler oauthHandler; private String[] earlyAccess; @@ -23,46 +22,33 @@ public OAuthInterceptor(OAuthHandler oauthHandler) { this.oauthHandler = oauthHandler; } - public void setEarlyAccess(String[] earlyAccess) { this.earlyAccess = earlyAccess; } - public boolean isOAuthConfigured() { return oauthHandler != null && oauthHandler.getConfig() != null; } - public boolean hasValidTokens() { - return oauthHandler != null && - oauthHandler.getTokens() != null && - !oauthHandler.getTokens().isExpired(); + return oauthHandler != null + && oauthHandler.getTokens() != null + && !oauthHandler.getTokens().isExpired(); } @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); - - System.out.println("\nOAuth Interceptor - Request Details:"); - System.out.println("URL: " + originalRequest.url()); - System.out.println("Method: " + originalRequest.method()); - System.out.println("Has Tokens: " + (oauthHandler.getTokens() != null)); - if (oauthHandler.getTokens() != null) { - System.out.println("Access Token: " + oauthHandler.getTokens().getAccessToken()); - System.out.println("Token Expired: " + oauthHandler.getTokens().isExpired()); - } - Request.Builder requestBuilder = originalRequest.newBuilder() - .header("X-User-Agent", Util.defaultUserAgent()) - .header("User-Agent", Util.defaultUserAgent()) - .header("Content-Type", originalRequest.url().toString().contains("/token") ? "application/x-www-form-urlencoded" : "application/json") - .header("X-Header-EA", earlyAccess != null ? String.join(",", earlyAccess) : "true"); + .header("X-User-Agent", Util.defaultUserAgent()) + .header("User-Agent", Util.defaultUserAgent()) + .header("Content-Type", originalRequest.url().toString().contains("/token") ? "application/x-www-form-urlencoded" : "application/json") + .header("x-header-ea", earlyAccess != null ? String.join(",", earlyAccess) : "true"); // Skip auth header for token endpoints if (!originalRequest.url().toString().contains("/token")) { if (oauthHandler.getTokens() != null && oauthHandler.getTokens().hasAccessToken()) { requestBuilder.header("Authorization", "Bearer " + oauthHandler.getAccessToken()); - System.out.println("Added Authorization header: Bearer " + oauthHandler.getAccessToken()); + } } @@ -83,18 +69,18 @@ private Response executeRequest(Chain chain, Request request, int retryCount) th // Check if we need to refresh the token before making the request if (oauthHandler.getTokens().isExpired()) { - + synchronized (refreshLock) { try { - logger.info("Token expired, refreshing..."); + oauthHandler.refreshAccessToken().get(30, TimeUnit.SECONDS); - + // Update authorization header with new token request = request.newBuilder() - .header("Authorization", "Bearer " + oauthHandler.getAccessToken()) - .build(); + .header("Authorization", "Bearer " + oauthHandler.getAccessToken()) + .build(); } catch (InterruptedException | ExecutionException | TimeoutException e) { - logger.severe("Token refresh failed: " + e.getMessage()); + throw new IOException("Failed to refresh access token", e); } } @@ -106,45 +92,33 @@ private Response executeRequest(Chain chain, Request request, int retryCount) th // Handle error responses if (!response.isSuccessful() && retryCount < MAX_RETRIES) { int code = response.code(); - String body = null; - try { - if (response.body() != null) { - body = response.body().string(); - } - } catch (IOException e) { - // Ignore body read errors - } response.close(); - - logger.info("Request failed with code " + code + ": " + body); // Handle 401 with token refresh - if (code == 401 && oauthHandler != null && oauthHandler.getTokens() != null && - oauthHandler.getTokens().hasRefreshToken()) { - + if (code == 401 && oauthHandler != null && oauthHandler.getTokens() != null + && oauthHandler.getTokens().hasRefreshToken()) { + synchronized (refreshLock) { try { - logger.info("Attempting token refresh after 401"); + oauthHandler.refreshAccessToken().get(30, TimeUnit.SECONDS); - + // Update authorization header with new token request = request.newBuilder() - .header("Authorization", "Bearer " + oauthHandler.getAccessToken()) - .build(); - + .header("Authorization", "Bearer " + oauthHandler.getAccessToken()) + .build(); + return executeRequest(chain, request, retryCount + 1); } catch (InterruptedException | ExecutionException | TimeoutException e) { throw new IOException("Failed to refresh access token after 401", e); } } } - + // Handle other retryable errors (429, 5xx) if ((code == 429 || code >= 500) && code != 501) { try { - // Exponential backoff long delay = Math.min(1000 * (1 << retryCount), 30000); - logger.info("Retrying request after " + delay + "ms delay"); Thread.sleep(delay); return executeRequest(chain, request, retryCount + 1); } catch (InterruptedException e) { @@ -156,4 +130,4 @@ private Response executeRequest(Chain chain, Request request, int retryCount) th return response; } -} \ No newline at end of file +} diff --git a/src/test/java/com/contentstack/cms/oauth/OAuthTest.java b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java new file mode 100644 index 00000000..0184bc0a --- /dev/null +++ b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java @@ -0,0 +1,438 @@ +package com.contentstack.cms.oauth; + +import java.io.IOException; +import java.util.concurrent.*; + +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import static org.mockito.ArgumentMatchers.any; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.junit.MockitoJUnitRunner; + +import com.contentstack.cms.Contentstack; +import com.contentstack.cms.models.OAuthConfig; +import com.contentstack.cms.models.OAuthTokens; +import com.google.gson.Gson; + +import okhttp3.*; +import okio.Buffer; + + +@RunWith(MockitoJUnitRunner.class) +public class OAuthTest { + + // Using lenient mocks to avoid unnecessary stubbing warnings + // when some mock setups are not used in every test + + private static final String TEST_APP_ID = "test_app_id"; + private static final String TEST_CLIENT_ID = "test_client_id"; + private static final String TEST_CLIENT_SECRET = "test_client_secret"; + private static final String TEST_REDIRECT_URI = "https://example.com/callback"; + private static final String TEST_AUTH_CODE = "test_auth_code"; + private static final String TEST_ACCESS_TOKEN = "test_access_token"; + private static final String TEST_REFRESH_TOKEN = "test_refresh_token"; + + @Mock(lenient = true) + private OkHttpClient mockHttpClient; + + @Mock(lenient = true) + private Call mockCall; + + @Mock(lenient = true) + private Response mockResponse; + + @Mock(lenient = true) + private ResponseBody mockResponseBody; + + private OAuthHandler pkceHandler; + private OAuthHandler clientSecretHandler; + private Contentstack pkceClient; + private Contentstack clientSecretClient; + private Gson gson; + + @Before + public void setup() { + gson = new Gson(); + + // Create OAuth configuration for PKCE (no client secret) + OAuthConfig pkceConfig = OAuthConfig.builder() + .appId(TEST_APP_ID) + .clientId(TEST_CLIENT_ID) + .redirectUri(TEST_REDIRECT_URI) + .build(); + + pkceHandler = new OAuthHandler(mockHttpClient, pkceConfig); + + // Create OAuth configuration with client secret + OAuthConfig clientSecretConfig = OAuthConfig.builder() + .appId(TEST_APP_ID) + .clientId(TEST_CLIENT_ID) + .clientSecret(TEST_CLIENT_SECRET) + .redirectUri(TEST_REDIRECT_URI) + .build(); + + clientSecretHandler = new OAuthHandler(mockHttpClient, clientSecretConfig); + + // Create Contentstack clients + pkceClient = new Contentstack.Builder() + .setOAuthWithPKCE(TEST_APP_ID, TEST_CLIENT_ID, TEST_REDIRECT_URI) + .build(); + + clientSecretClient = new Contentstack.Builder() + .setOAuth(TEST_APP_ID, TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI) + .build(); + } + + // ================= + // CONFIGURATION TESTS + // ================= + + @Test + public void testPKCEConfiguration() { + assertTrue("PKCE OAuth should be configured", pkceClient.isOAuthConfigured()); + assertNotNull("PKCE OAuth handler should exist", pkceClient.getOAuthHandler()); + assertFalse("Should not have tokens initially", pkceClient.hasValidOAuthTokens()); + assertTrue("Should be PKCE enabled", pkceHandler.getConfig().isPkceEnabled()); + } + + @Test + public void testClientSecretConfiguration() { + assertTrue("Client Secret OAuth should be configured", clientSecretClient.isOAuthConfigured()); + assertNotNull("Client Secret OAuth handler should exist", clientSecretClient.getOAuthHandler()); + assertFalse("Should not have tokens initially", clientSecretClient.hasValidOAuthTokens()); + assertFalse("Should not be PKCE enabled", clientSecretHandler.getConfig().isPkceEnabled()); + } + + @Test + public void testInvalidConfigurations() { + // Test invalid app ID + try { + new Contentstack.Builder() + .setOAuthWithPKCE("", TEST_CLIENT_ID, TEST_REDIRECT_URI) + .build(); + fail("Should throw exception for empty app ID"); + } catch (IllegalArgumentException e) { + assertTrue("Should mention app ID", e.getMessage().contains("appId")); + } + + // Test invalid client ID + try { + new Contentstack.Builder() + .setOAuth(TEST_APP_ID, "", TEST_CLIENT_SECRET, TEST_REDIRECT_URI) + .build(); + fail("Should throw exception for empty client ID"); + } catch (IllegalArgumentException e) { + assertTrue("Should mention client ID", e.getMessage().contains("clientId")); + } + + // Test invalid redirect URI + try { + new Contentstack.Builder() + .setOAuth(TEST_APP_ID, TEST_CLIENT_ID, TEST_CLIENT_SECRET, "invalid-url") + .build(); + fail("Should throw exception for invalid redirect URI"); + } catch (IllegalArgumentException e) { + assertTrue("Should mention redirect URI", e.getMessage().contains("redirectUri")); + } + } + + // ================= + // AUTHORIZATION URL TESTS + // ================= + + @Test + public void testPKCEAuthorizationUrlGeneration() { + String authUrl = pkceHandler.authorize(); + + assertNotNull("Authorization URL should not be null", authUrl); + assertTrue("URL should contain app ID", authUrl.contains("app_id=" + TEST_APP_ID)); + assertTrue("URL should contain client ID", authUrl.contains("client_id=" + TEST_CLIENT_ID)); + assertTrue("URL should contain redirect URI", authUrl.contains("redirect_uri=")); + assertTrue("URL should contain PKCE code challenge", authUrl.contains("code_challenge=")); + assertTrue("URL should contain PKCE method", authUrl.contains("code_challenge_method=S256")); + assertTrue("URL should contain response type", authUrl.contains("response_type=code")); + assertFalse("URL should not contain client_secret", authUrl.contains("client_secret")); + } + + @Test + public void testClientSecretAuthorizationUrlGeneration() { + String authUrl = clientSecretHandler.authorize(); + + assertNotNull("Authorization URL should not be null", authUrl); + assertTrue("URL should contain app ID", authUrl.contains("app_id=" + TEST_APP_ID)); + assertTrue("URL should contain client ID", authUrl.contains("client_id=" + TEST_CLIENT_ID)); + assertTrue("URL should contain redirect URI", authUrl.contains("redirect_uri=")); + assertTrue("URL should contain response type", authUrl.contains("response_type=code")); + assertFalse("URL should not contain PKCE code challenge", authUrl.contains("code_challenge=")); + assertFalse("URL should not contain PKCE method", authUrl.contains("code_challenge_method")); + } + + @Test + public void testAuthUrlUniqueness() { + // Create two PKCE handlers and verify they generate different URLs due to different code challenges + OAuthConfig config1 = OAuthConfig.builder() + .appId(TEST_APP_ID) + .clientId(TEST_CLIENT_ID) + .redirectUri(TEST_REDIRECT_URI) + .build(); + + OAuthConfig config2 = OAuthConfig.builder() + .appId(TEST_APP_ID) + .clientId(TEST_CLIENT_ID) + .redirectUri(TEST_REDIRECT_URI) + .build(); + + OAuthHandler handler1 = new OAuthHandler(mockHttpClient, config1); + OAuthHandler handler2 = new OAuthHandler(mockHttpClient, config2); + + String url1 = handler1.authorize(); + String url2 = handler2.authorize(); + + assertNotEquals("URLs should be different due to different PKCE challenges", url1, url2); + } + + // ================= + // TOKEN EXCHANGE TESTS + // ================= + + @Test + public void testSuccessfulPKCETokenExchange() throws IOException, ExecutionException, InterruptedException { + setupSuccessfulTokenResponse(); + + CompletableFuture future = pkceHandler.exchangeCodeForToken(TEST_AUTH_CODE); + OAuthTokens tokens = future.get(); + + verifyTokensValid(tokens); + verifyPKCETokenRequest(); + } + + @Test + public void testSuccessfulClientSecretTokenExchange() throws IOException, ExecutionException, InterruptedException { + setupSuccessfulTokenResponse(); + + CompletableFuture future = clientSecretHandler.exchangeCodeForToken(TEST_AUTH_CODE); + OAuthTokens tokens = future.get(); + + verifyTokensValid(tokens); + verifyClientSecretTokenRequest(); + } + + @Test + public void testInvalidAuthCode() { + CompletableFuture nullFuture = pkceHandler.exchangeCodeForToken(null); + assertTrue("Future should be completed exceptionally for null code", nullFuture.isCompletedExceptionally()); + + CompletableFuture emptyFuture = pkceHandler.exchangeCodeForToken(" "); + assertTrue("Future should be completed exceptionally for empty code", emptyFuture.isCompletedExceptionally()); + } + + // ================= + // TOKEN REFRESH TESTS + // ================= + + @Test + public void testPKCETokenRefresh() throws IOException, ExecutionException, InterruptedException { + // Setup initial expired tokens + OAuthTokens expiredTokens = createExpiredTokens(); + pkceHandler.setTokens(expiredTokens); + + setupSuccessfulRefreshResponse("new_pkce_access", "new_pkce_refresh"); + + CompletableFuture future = pkceHandler.refreshAccessToken(); + OAuthTokens newTokens = future.get(); + + verifyRefreshedTokens(newTokens, "new_pkce_access", "new_pkce_refresh"); + verifyRefreshRequest(); + } + + @Test + public void testClientSecretTokenRefresh() throws IOException, ExecutionException, InterruptedException { + // Setup initial expired tokens + OAuthTokens expiredTokens = createExpiredTokens(); + clientSecretHandler.setTokens(expiredTokens); + + setupSuccessfulRefreshResponse("new_secret_access", "new_secret_refresh"); + + CompletableFuture future = clientSecretHandler.refreshAccessToken(); + OAuthTokens newTokens = future.get(); + + verifyRefreshedTokens(newTokens, "new_secret_access", "new_secret_refresh"); + verifyRefreshRequestWithSecret(); + } + + @Test + public void testRefreshWithoutTokens() { + CompletableFuture future = pkceHandler.refreshAccessToken(); + + assertTrue("Future should complete exceptionally without tokens", future.isCompletedExceptionally()); + + try { + future.get(); + fail("Should have thrown exception for missing tokens"); + } catch (ExecutionException | InterruptedException e) { + assertTrue("Should be IllegalStateException", e.getCause() instanceof IllegalStateException); + } + } + + + // ================= + // HELPER METHODS + // ================= + + private OAuthTokens createValidTokens() { + OAuthTokens tokens = new OAuthTokens(); + tokens.setAccessToken(TEST_ACCESS_TOKEN); + tokens.setRefreshToken(TEST_REFRESH_TOKEN); + tokens.setTokenType("Bearer"); + tokens.setExpiresIn(3600L); // 1 hour + tokens.setScope("read write"); + return tokens; + } + + private OAuthTokens createExpiredTokens() { + OAuthTokens tokens = createValidTokens(); + tokens.setExpiresIn(1L); // Make it expire quickly + // Wait to ensure expiration + try { + Thread.sleep(3500); // Wait longer than 1 second + 2 minute buffer + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return tokens; + } + + private void setupSuccessfulTokenResponse() throws IOException { + OAuthTokens expectedTokens = createValidTokens(); + String tokenResponse = gson.toJson(expectedTokens); + + when(mockHttpClient.newCall(any(Request.class))).thenReturn(mockCall); + when(mockCall.execute()).thenReturn(mockResponse); + when(mockResponse.isSuccessful()).thenReturn(true); + when(mockResponse.body()).thenReturn(mockResponseBody); + when(mockResponse.headers()).thenReturn(Headers.of()); + when(mockResponse.code()).thenReturn(200); + when(mockResponseBody.string()).thenReturn(tokenResponse); + } + + private void setupSuccessfulRefreshResponse(String newAccessToken, String newRefreshToken) throws IOException { + OAuthTokens refreshedTokens = createValidTokens(); + refreshedTokens.setAccessToken(newAccessToken); + refreshedTokens.setRefreshToken(newRefreshToken); + String refreshResponse = gson.toJson(refreshedTokens); + + when(mockHttpClient.newCall(any(Request.class))).thenReturn(mockCall); + when(mockCall.execute()).thenReturn(mockResponse); + when(mockResponse.isSuccessful()).thenReturn(true); + when(mockResponse.body()).thenReturn(mockResponseBody); + when(mockResponse.headers()).thenReturn(Headers.of()); + when(mockResponse.code()).thenReturn(200); + when(mockResponseBody.string()).thenReturn(refreshResponse); + } + + private void setupHttpErrorResponse(int statusCode, String errorMessage) throws IOException { + when(mockHttpClient.newCall(any(Request.class))).thenReturn(mockCall); + when(mockCall.execute()).thenReturn(mockResponse); + when(mockResponse.isSuccessful()).thenReturn(false); + when(mockResponse.code()).thenReturn(statusCode); + when(mockResponse.message()).thenReturn(errorMessage); + when(mockResponse.headers()).thenReturn(Headers.of()); + when(mockResponse.body()).thenReturn(mockResponseBody); + when(mockResponseBody.string()).thenReturn(errorMessage); + } + + private void setupMalformedJsonResponse() throws IOException { + when(mockHttpClient.newCall(any(Request.class))).thenReturn(mockCall); + when(mockCall.execute()).thenReturn(mockResponse); + when(mockResponse.isSuccessful()).thenReturn(true); + when(mockResponse.body()).thenReturn(mockResponseBody); + when(mockResponse.headers()).thenReturn(Headers.of()); + when(mockResponse.code()).thenReturn(200); + when(mockResponseBody.string()).thenReturn("invalid json"); + } + + private void verifyTokensValid(OAuthTokens tokens) { + assertNotNull("Tokens should not be null", tokens); + assertEquals("Access token should match", TEST_ACCESS_TOKEN, tokens.getAccessToken()); + assertEquals("Refresh token should match", TEST_REFRESH_TOKEN, tokens.getRefreshToken()); + assertEquals("Token type should be Bearer", "Bearer", tokens.getTokenType()); + assertTrue("Tokens should be valid", tokens.isValid()); + } + + private void verifyRefreshedTokens(OAuthTokens newTokens, String expectedAccessToken, String expectedRefreshToken) { + assertNotNull("New tokens should not be null", newTokens); + assertEquals("New access token should match", expectedAccessToken, newTokens.getAccessToken()); + assertEquals("New refresh token should match", expectedRefreshToken, newTokens.getRefreshToken()); + assertNotEquals("Access token should be different", TEST_ACCESS_TOKEN, newTokens.getAccessToken()); + } + + private void verifyPKCETokenRequest() { + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class); + verify(mockHttpClient).newCall(requestCaptor.capture()); + + Request capturedRequest = requestCaptor.getValue(); + assertEquals("POST", capturedRequest.method()); + assertTrue("URL should be token endpoint", + capturedRequest.url().toString().contains("/token")); + + String requestBody = getRequestBody(capturedRequest); + assertTrue("Should contain grant_type", requestBody.contains("grant_type=authorization_code")); + assertTrue("Should contain code", requestBody.contains("code=" + TEST_AUTH_CODE)); + assertTrue("Should contain code_verifier for PKCE", requestBody.contains("code_verifier=")); + assertFalse("Should not contain client_secret", requestBody.contains("client_secret=")); + assertTrue("Should contain client_id", requestBody.contains("client_id=" + TEST_CLIENT_ID)); + assertTrue("Should contain redirect_uri", requestBody.contains("redirect_uri=")); + } + + private void verifyClientSecretTokenRequest() { + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class); + verify(mockHttpClient).newCall(requestCaptor.capture()); + + Request capturedRequest = requestCaptor.getValue(); + String requestBody = getRequestBody(capturedRequest); + assertTrue("Should contain grant_type", requestBody.contains("grant_type=authorization_code")); + assertTrue("Should contain code", requestBody.contains("code=" + TEST_AUTH_CODE)); + assertTrue("Should contain client_secret", requestBody.contains("client_secret=" + TEST_CLIENT_SECRET)); + assertFalse("Should not contain code_verifier", requestBody.contains("code_verifier=")); + assertTrue("Should contain client_id", requestBody.contains("client_id=" + TEST_CLIENT_ID)); + assertTrue("Should contain redirect_uri", requestBody.contains("redirect_uri=")); + } + + private void verifyRefreshRequest() { + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class); + verify(mockHttpClient).newCall(requestCaptor.capture()); + + Request capturedRequest = requestCaptor.getValue(); + String requestBody = getRequestBody(capturedRequest); + assertTrue("Should contain grant_type=refresh_token", requestBody.contains("grant_type=refresh_token")); + assertTrue("Should contain refresh_token", requestBody.contains("refresh_token=" + TEST_REFRESH_TOKEN)); + } + + private void verifyRefreshRequestWithSecret() { + verifyRefreshRequest(); + + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class); + verify(mockHttpClient).newCall(requestCaptor.capture()); + + Request capturedRequest = requestCaptor.getValue(); + String requestBody = getRequestBody(capturedRequest); + assertTrue("Should contain client_secret", requestBody.contains("client_secret=" + TEST_CLIENT_SECRET)); + assertTrue("Should contain client_id", requestBody.contains("client_id=" + TEST_CLIENT_ID)); + } + + private String getRequestBody(Request request) { + try { + RequestBody body = request.body(); + if (body == null) return ""; + + Buffer buffer = new Buffer(); + body.writeTo(buffer); + return buffer.readUtf8(); + } catch (IOException e) { + return ""; + } + } +} From 7d00b9f2630993ae9c432ce1e681710c1091ec76 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 3 Sep 2025 16:56:03 +0530 Subject: [PATCH 138/197] refactor: Move OAuth error messages to constants and clean up imports --- .../com/contentstack/cms/Contentstack.java | 23 ++++++++----------- .../java/com/contentstack/cms/core/Util.java | 17 ++++++++++++++ .../contentstack/cms/models/OAuthConfig.java | 10 ++++---- .../contentstack/cms/oauth/OAuthHandler.java | 13 ++++++----- .../cms/oauth/OAuthInterceptor.java | 6 ++--- 5 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 3b6e2e69..043ccb4b 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -5,7 +5,6 @@ import java.time.Duration; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; @@ -17,8 +16,6 @@ import static com.contentstack.cms.core.Util.API_KEY; import static com.contentstack.cms.core.Util.AUTHORIZATION; import static com.contentstack.cms.core.Util.BRANCH; -import static com.contentstack.cms.core.Util.ILLEGAL_USER; -import static com.contentstack.cms.core.Util.PLEASE_LOGIN; import com.contentstack.cms.models.Error; import com.contentstack.cms.models.LoginDetails; import com.contentstack.cms.models.OAuthConfig; @@ -99,7 +96,7 @@ public class Contentstack { */ public User user() { if (!isOAuthConfigured() && this.authtoken == null) { - throw new IllegalStateException("Please login or configure OAuth to access user"); + throw new IllegalStateException(Util.OAUTH_LOGIN_REQUIRED + " user"); } user = new User(this.instance); return user; @@ -293,7 +290,7 @@ Response logoutWithAuthtoken(String authtoken) throws IOException */ public Organization organization() { if (!isOAuthConfigured() && this.authtoken == null) { - throw new IllegalStateException("Please login or configure OAuth to access organization"); + throw new IllegalStateException(Util.OAUTH_LOGIN_REQUIRED + " organization"); } // If using OAuth, get organization from tokens @@ -328,10 +325,10 @@ public Organization organization() { */ public Organization organization(@NotNull String organizationUid) { if (!isOAuthConfigured() && this.authtoken == null) { - throw new IllegalStateException("Please login or configure OAuth to access organization"); + throw new IllegalStateException(Util.OAUTH_LOGIN_REQUIRED + " organization"); } if (organizationUid.isEmpty()) { - throw new IllegalStateException("organizationUid can not be empty"); + throw new IllegalStateException(Util.OAUTH_ORG_EMPTY); } return new Organization(this.instance, organizationUid); } @@ -355,7 +352,7 @@ public Organization organization(@NotNull String organizationUid) { */ public Stack stack() { if (!isOAuthConfigured() && this.authtoken == null) { - throw new IllegalStateException("Please login or configure OAuth to access stack"); + throw new IllegalStateException(Util.OAUTH_LOGIN_REQUIRED + " stack"); } return new Stack(this.instance); } @@ -380,7 +377,7 @@ public Stack stack() { */ public Stack stack(@NotNull Map header) { if (!isOAuthConfigured() && this.authtoken == null && !header.containsKey(AUTHORIZATION)) { - throw new IllegalStateException("Please login or configure OAuth to access stack"); + throw new IllegalStateException(Util.OAUTH_LOGIN_REQUIRED + " stack"); } return new Stack(this.instance, header); } @@ -476,7 +473,7 @@ public Stack stack(@NotNull String apiKey, @NotNull String managementToken, @Not */ public String getOAuthAuthorizationUrl() { if (!isOAuthConfigured()) { - throw new IllegalStateException("OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()"); + throw new IllegalStateException(Util.OAUTH_CONFIG_MISSING); } return oauthHandler.authorize(); } @@ -489,7 +486,7 @@ public String getOAuthAuthorizationUrl() { */ public CompletableFuture exchangeOAuthCode(String code) { if (!isOAuthConfigured()) { - throw new IllegalStateException("OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()"); + throw new IllegalStateException(Util.OAUTH_CONFIG_MISSING); } return oauthHandler.exchangeCodeForToken(code); } @@ -501,7 +498,7 @@ public CompletableFuture exchangeOAuthCode(String code) { */ public CompletableFuture refreshOAuthToken() { if (!isOAuthConfigured()) { - throw new IllegalStateException("OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()"); + throw new IllegalStateException(Util.OAUTH_CONFIG_MISSING); } return oauthHandler.refreshAccessToken(); } @@ -545,7 +542,7 @@ public OAuthHandler getOAuthHandler() { */ public CompletableFuture oauthLogout(boolean revokeAuthorization) { if (!isOAuthConfigured()) { - throw new IllegalStateException("OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()"); + throw new IllegalStateException(Util.OAUTH_CONFIG_MISSING); } return oauthHandler.logout(revokeAuthorization); } diff --git a/src/main/java/com/contentstack/cms/core/Util.java b/src/main/java/com/contentstack/cms/core/Util.java index bd8fe841..11f36939 100644 --- a/src/main/java/com/contentstack/cms/core/Util.java +++ b/src/main/java/com/contentstack/cms/core/Util.java @@ -51,6 +51,23 @@ public class Util { public static final String ERROR_INSTALLATION = "installation uid is required"; public static final String MISSING_ORG_ID = "organization uid is required"; + // OAuth Constants + public static final String OAUTH_APP_HOST = "app.contentstack.com"; + public static final String OAUTH_API_HOST = "developerhub-api.contentstack.com"; + public static final String OAUTH_TOKEN_ENDPOINT = "/token"; + public static final String OAUTH_AUTHORIZE_ENDPOINT = "/#!/apps/%s/authorize"; + + // OAuth Error Messages + public static final String OAUTH_NO_TOKENS = "No OAuth tokens available. Please authenticate first."; + public static final String OAUTH_NO_REFRESH_TOKEN = "No refresh token available"; + public static final String OAUTH_EMPTY_CODE = "Authorization code cannot be null or empty"; + public static final String OAUTH_CONFIG_MISSING = "OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()"; + public static final String OAUTH_REFRESH_FAILED = "Failed to refresh access token"; + public static final String OAUTH_REVOKE_FAILED = "Failed to revoke authorization"; + public static final String OAUTH_STATUS_FAILED = "Failed to get authorization status"; + public static final String OAUTH_LOGIN_REQUIRED = "Please login or configure OAuth to access"; + public static final String OAUTH_ORG_EMPTY = "organizationUid can not be empty"; + // The code `Util() throws IllegalAccessException` is a constructor for the // `Util` class that throws an // `IllegalAccessException` when called. This constructor is marked as private, diff --git a/src/main/java/com/contentstack/cms/models/OAuthConfig.java b/src/main/java/com/contentstack/cms/models/OAuthConfig.java index af1b99d9..f5360c22 100644 --- a/src/main/java/com/contentstack/cms/models/OAuthConfig.java +++ b/src/main/java/com/contentstack/cms/models/OAuthConfig.java @@ -8,6 +8,8 @@ import java.util.Arrays; import java.util.List; +import com.contentstack.cms.core.Util; + /** * Configuration class for OAuth 2.0 authentication */ @@ -67,7 +69,7 @@ public String getFormattedAuthorizationEndpoint() { return authEndpoint; } - String hostname = "app.contentstack.com"; + String hostname = Util.OAUTH_APP_HOST; // Transform hostname if needed if (hostname.contains("contentstack")) { @@ -76,7 +78,7 @@ public String getFormattedAuthorizationEndpoint() { .replaceAll("\\.io$", ".com"); // *.io -> *.com } - return "https://" + hostname + "/#!/apps/" + appId + "/authorize"; + return "https://" + hostname + String.format(Util.OAUTH_AUTHORIZE_ENDPOINT, appId); } /** @@ -89,7 +91,7 @@ public String getTokenEndpoint() { return tokenEndpoint; } - String hostname = "developerhub-api.contentstack.com"; + String hostname = Util.OAUTH_API_HOST; // Transform hostname if needed if (hostname.contains("contentstack")) { @@ -98,7 +100,7 @@ public String getTokenEndpoint() { .replaceAll("\\.io$", ".com"); // *.io -> *.com } - return "https://" + hostname + "/token"; + return "https://" + hostname + Util.OAUTH_TOKEN_ENDPOINT; } /** diff --git a/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java b/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java index e969ebbc..6e450c2c 100644 --- a/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java +++ b/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java @@ -10,6 +10,7 @@ import java.util.Date; import java.util.concurrent.CompletableFuture; +import com.contentstack.cms.core.Util; import com.contentstack.cms.models.OAuthConfig; import com.contentstack.cms.models.OAuthTokens; import com.google.gson.Gson; @@ -154,7 +155,7 @@ public String authorize() { */ public CompletableFuture exchangeCodeForToken(String code) { if (code == null || code.trim().isEmpty()) { - return CompletableFuture.failedFuture(new IllegalArgumentException("Authorization code cannot be null or empty")); + return CompletableFuture.failedFuture(new IllegalArgumentException(Util.OAUTH_EMPTY_CODE)); } return CompletableFuture.supplyAsync(() -> { try { @@ -210,11 +211,11 @@ public CompletableFuture refreshAccessToken() { // Check if we have tokens and refresh token if (tokens == null) { return CompletableFuture.failedFuture( - new IllegalStateException("No tokens available")); + new IllegalStateException(Util.OAUTH_NO_TOKENS)); } if (!tokens.hasRefreshToken()) { return CompletableFuture.failedFuture( - new IllegalStateException("No refresh token available")); + new IllegalStateException(Util.OAUTH_NO_REFRESH_TOKEN)); } // Check if token is actually expired @@ -249,7 +250,7 @@ public CompletableFuture refreshAccessToken() { return newTokens; } catch (IOException | RuntimeException e) { - throw new RuntimeException("Failed to refresh tokens", e); + throw new RuntimeException(Util.OAUTH_REFRESH_FAILED, e); } }); } @@ -343,7 +344,7 @@ public CompletableFuture getOauthAppAuthorization() { if (!response.isSuccessful()) { String error = responseBody != null ? responseBody.string() : "Unknown error"; - throw new RuntimeException("Failed to get authorization status: " + error); + throw new RuntimeException(Util.OAUTH_STATUS_FAILED + ": " + error); } String body = responseBody != null ? responseBody.string() : "{}"; @@ -386,7 +387,7 @@ public CompletableFuture revokeOauthAppAuthorization() { if (!response.isSuccessful()) { String error = responseBody != null ? responseBody.string() : "Unknown error"; - throw new RuntimeException("Failed to revoke authorization: " + error); + throw new RuntimeException(Util.OAUTH_REVOKE_FAILED + ": " + error); } } finally { if (responseBody != null) { diff --git a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java index df520748..baf3997b 100644 --- a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java +++ b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java @@ -64,7 +64,7 @@ private Response executeRequest(Chain chain, Request request, int retryCount) th // Ensure we have tokens if (oauthHandler == null || oauthHandler.getTokens() == null) { - throw new IOException("No OAuth tokens available. Please authenticate first."); + throw new IOException(Util.OAUTH_NO_TOKENS); } // Check if we need to refresh the token before making the request @@ -81,7 +81,7 @@ private Response executeRequest(Chain chain, Request request, int retryCount) th .build(); } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new IOException("Failed to refresh access token", e); + throw new IOException(Util.OAUTH_REFRESH_FAILED, e); } } } @@ -110,7 +110,7 @@ private Response executeRequest(Chain chain, Request request, int retryCount) th return executeRequest(chain, request, retryCount + 1); } catch (InterruptedException | ExecutionException | TimeoutException e) { - throw new IOException("Failed to refresh access token after 401", e); + throw new IOException(Util.OAUTH_REFRESH_FAILED + " after 401", e); } } } From 0e0ee772cf612926fc655fad05fbb45c92cbdcf5 Mon Sep 17 00:00:00 2001 From: Aravind Kumar Date: Mon, 8 Sep 2025 22:15:02 +0530 Subject: [PATCH 139/197] Delete secrets-scan.yml --- .github/workflows/secrets-scan.yml | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 .github/workflows/secrets-scan.yml diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml deleted file mode 100644 index 049c02f4..00000000 --- a/.github/workflows/secrets-scan.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Secrets Scan -on: - pull_request: - types: [opened, synchronize, reopened] -jobs: - security-secrets: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: '2' - ref: '${{ github.event.pull_request.head.ref }}' - - run: | - git reset --soft HEAD~1 - - name: Install Talisman - run: | - # Download Talisman - wget https://github.com/thoughtworks/talisman/releases/download/v1.37.0/talisman_linux_amd64 -O talisman - - # Checksum verification - checksum=$(sha256sum ./talisman | awk '{print $1}') - if [ "$checksum" != "8e0ae8bb7b160bf10c4fa1448beb04a32a35e63505b3dddff74a092bccaaa7e4" ]; then exit 1; fi - - # Make it executable - chmod +x talisman - - name: Run talisman - run: | - # Run Talisman with the pre-commit hook - ./talisman --githook pre-commit \ No newline at end of file From 70c2f14a99b94a56f32db20ecbcde175b9994646 Mon Sep 17 00:00:00 2001 From: Aravind Kumar Date: Mon, 8 Sep 2025 22:15:06 +0530 Subject: [PATCH 140/197] Updated codeowners --- .github/CODEOWNERS | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1be7e0dc..785adb40 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,9 @@ -* @contentstack/security-admin +* @contentstack/devex-pr-reviewers + +.github/workflows/sca-scan.yml @contentstack/security-admin + +**/.snyk @contentstack/security-admin + +.github/workflows/policy-scan.yml @contentstack/security-admin + +.github/workflows/issues-jira.yml @contentstack/security-admin From 4626beb615152dc7e8957d1c410d633b8ad93128 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 9 Sep 2025 13:45:30 +0530 Subject: [PATCH 141/197] fix: Update OAuth host handling to support for regions --- .../com/contentstack/cms/Contentstack.java | 27 ++++ .../contentstack/cms/models/OAuthConfig.java | 21 ++- .../com/contentstack/cms/oauth/OAuthTest.java | 149 ++++++++++++++++++ 3 files changed, 190 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 043ccb4b..09afff39 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -748,11 +748,25 @@ public Builder setOAuthConfig(OAuthConfig config) { * @return Builder instance */ public Builder setOAuth(String appId, String clientId, String clientSecret, String redirectUri) { + return setOAuth(appId, clientId, clientSecret, redirectUri, this.hostname); + } + + /** + * Configures OAuth with client credentials and specific host + * @param appId Application ID + * @param clientId Client ID + * @param clientSecret Client secret + * @param redirectUri Redirect URI + * @param host API host (e.g. "api.contentstack.io", "eu-api.contentstack.com") + * @return Builder instance + */ + public Builder setOAuth(String appId, String clientId, String clientSecret, String redirectUri, String host) { this.oauthConfig = OAuthConfig.builder() .appId(appId) .clientId(clientId) .clientSecret(clientSecret) .redirectUri(redirectUri) + .host(host) .build(); return this; } @@ -765,10 +779,23 @@ public Builder setOAuth(String appId, String clientId, String clientSecret, Stri * @return Builder instance */ public Builder setOAuthWithPKCE(String appId, String clientId, String redirectUri) { + return setOAuthWithPKCE(appId, clientId, redirectUri, this.hostname); + } + + /** + * Configures OAuth with PKCE (no client secret) and specific host + * @param appId Application ID + * @param clientId Client ID + * @param redirectUri Redirect URI + * @param host API host (e.g. "api.contentstack.io", "eu-api.contentstack.com") + * @return Builder instance + */ + public Builder setOAuthWithPKCE(String appId, String clientId, String redirectUri, String host) { this.oauthConfig = OAuthConfig.builder() .appId(appId) .clientId(clientId) .redirectUri(redirectUri) + .host(host) .build(); return this; } diff --git a/src/main/java/com/contentstack/cms/models/OAuthConfig.java b/src/main/java/com/contentstack/cms/models/OAuthConfig.java index f5360c22..b13119d9 100644 --- a/src/main/java/com/contentstack/cms/models/OAuthConfig.java +++ b/src/main/java/com/contentstack/cms/models/OAuthConfig.java @@ -25,6 +25,7 @@ public class OAuthConfig { private final String scope; private final String authEndpoint; private final String tokenEndpoint; + private final String host; /** * Validates the configuration @@ -69,13 +70,16 @@ public String getFormattedAuthorizationEndpoint() { return authEndpoint; } - String hostname = Util.OAUTH_APP_HOST; + String hostname = host != null ? host : Util.OAUTH_APP_HOST; // Transform hostname if needed if (hostname.contains("contentstack")) { hostname = hostname - .replaceAll("^api\\.", "app.") // api.contentstack -> app.contentstack - .replaceAll("\\.io$", ".com"); // *.io -> *.com + .replaceAll("-api\\.", "-app.") // eu-api.contentstack.com -> eu-app.contentstack.com + .replaceAll("^api\\.", "app.") // api.contentstack.io -> app.contentstack.io + .replaceAll("\\.io$", ".com"); // *.io -> *.com + } else { + hostname = Util.OAUTH_APP_HOST; } return "https://" + hostname + String.format(Util.OAUTH_AUTHORIZE_ENDPOINT, appId); @@ -91,13 +95,16 @@ public String getTokenEndpoint() { return tokenEndpoint; } - String hostname = Util.OAUTH_API_HOST; + String hostname = host != null ? host : Util.OAUTH_API_HOST; // Transform hostname if needed if (hostname.contains("contentstack")) { - hostname = hostname - .replaceAll("^dev\\d+\\.", "dev.") // dev1.* -> dev.* - .replaceAll("\\.io$", ".com"); // *.io -> *.com + hostname = hostname + .replaceAll("-api\\.", "-developerhub-api.") // eu-api.contentstack.com -> eu-developerhub-api.contentstack.com + .replaceAll("^api\\.", "developerhub-api.") // api.contentstack.io -> developerhub-api.contentstack.io + .replaceAll("\\.io$", ".com"); // *.io -> *.com + } else { + hostname = Util.OAUTH_API_HOST; } return "https://" + hostname + Util.OAUTH_TOKEN_ENDPOINT; diff --git a/src/test/java/com/contentstack/cms/oauth/OAuthTest.java b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java index 0184bc0a..245d6db4 100644 --- a/src/test/java/com/contentstack/cms/oauth/OAuthTest.java +++ b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java @@ -14,6 +14,7 @@ import org.mockito.junit.MockitoJUnitRunner; import com.contentstack.cms.Contentstack; +import com.contentstack.cms.core.Util; import com.contentstack.cms.models.OAuthConfig; import com.contentstack.cms.models.OAuthTokens; import com.google.gson.Gson; @@ -195,6 +196,154 @@ public void testAuthUrlUniqueness() { assertNotEquals("URLs should be different due to different PKCE challenges", url1, url2); } + @Test + public void testDefaultOAuthEndpoints() { + // Test with default hosts + OAuthConfig config = OAuthConfig.builder() + .appId(TEST_APP_ID) + .clientId(TEST_CLIENT_ID) + .redirectUri(TEST_REDIRECT_URI) + .build(); + OAuthHandler handler = new OAuthHandler(mockHttpClient, config); + + String authUrl = handler.authorize(); + String tokenUrl = config.getTokenEndpoint(); + + assertTrue("Auth URL should use default app host", + authUrl.contains(Util.OAUTH_APP_HOST)); + assertTrue("Token URL should use default API host", + tokenUrl.contains(Util.OAUTH_API_HOST)); + } + + @Test + public void testHostTransformations() { + // Test cases: {API Host, Expected App Host, Expected Token Host} + String[][] testCases = { + // Default region + {"api.contentstack.io", "app.contentstack.com", "developerhub-api.contentstack.com"}, + {"api-contentstack.com", "app-contentstack.com", "developerhub-api-contentstack.com"}, + + // AWS regions + {"eu-api.contentstack.com", "eu-app.contentstack.com", "eu-developerhub-api.contentstack.com"}, + {"eu-api-contentstack.com", "eu-app-contentstack.com", "eu-developerhub-api-contentstack.com"}, + {"au-api.contentstack.com", "au-app.contentstack.com", "au-developerhub-api.contentstack.com"}, + {"au-api-contentstack.com", "au-app-contentstack.com", "au-developerhub-api-contentstack.com"}, + + // Azure regions + {"azure-na-api.contentstack.com", "azure-na-app.contentstack.com", "azure-na-developerhub-api.contentstack.com"}, + {"azure-na-api-contentstack.com", "azure-na-app-contentstack.com", "azure-na-developerhub-api-contentstack.com"}, + {"azure-eu-api.contentstack.com", "azure-eu-app.contentstack.com", "azure-eu-developerhub-api.contentstack.com"}, + {"azure-eu-api-contentstack.com", "azure-eu-app-contentstack.com", "azure-eu-developerhub-api-contentstack.com"}, + + // GCP regions + {"gcp-na-api.contentstack.com", "gcp-na-app.contentstack.com", "gcp-na-developerhub-api.contentstack.com"}, + {"gcp-na-api-contentstack.com", "gcp-na-app-contentstack.com", "gcp-na-developerhub-api-contentstack.com"}, + {"gcp-eu-api.contentstack.com", "gcp-eu-app.contentstack.com", "gcp-eu-developerhub-api.contentstack.com"}, + {"gcp-eu-api-contentstack.com", "gcp-eu-app-contentstack.com", "gcp-eu-developerhub-api-contentstack.com"} + }; + + for (String[] testCase : testCases) { + String apiHost = testCase[0]; + String expectedAppHost = testCase[1]; + String expectedTokenHost = testCase[2]; + + OAuthConfig config = OAuthConfig.builder() + .appId(TEST_APP_ID) + .clientId(TEST_CLIENT_ID) + .redirectUri(TEST_REDIRECT_URI) + .host(apiHost) + .build(); + OAuthHandler handler = new OAuthHandler(mockHttpClient, config); + + String authUrl = handler.authorize(); + String tokenUrl = config.getTokenEndpoint(); + + assertTrue(String.format("Auth URL for %s should contain %s", apiHost, expectedAppHost), + authUrl.contains(expectedAppHost)); + assertTrue(String.format("Token URL for %s should contain %s", apiHost, expectedTokenHost), + tokenUrl.contains(expectedTokenHost)); + } + } + + @Test + public void testHostStorage() { + String testHost = "eu-api.contentstack.com"; + + // Test host storage in OAuthConfig + OAuthConfig config = OAuthConfig.builder() + .appId(TEST_APP_ID) + .clientId(TEST_CLIENT_ID) + .redirectUri(TEST_REDIRECT_URI) + .host(testHost) + .build(); + + assertEquals("Host should be stored in OAuthConfig", + testHost, config.getHost()); + + // Test host storage via Contentstack.Builder + Contentstack client = new Contentstack.Builder() + .setOAuth(TEST_APP_ID, TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI, testHost) + .build(); + + String authUrl = client.getOAuthAuthorizationUrl(); + assertTrue("Auth URL should use stored host", + authUrl.contains("eu-app.contentstack.com")); + + // Test host storage via PKCE builder + client = new Contentstack.Builder() + .setOAuthWithPKCE(TEST_APP_ID, TEST_CLIENT_ID, TEST_REDIRECT_URI, testHost) + .build(); + + authUrl = client.getOAuthAuthorizationUrl(); + assertTrue("Auth URL should use stored host with PKCE", + authUrl.contains("eu-app.contentstack.com")); + } + + @Test + public void testDefaultHosts() { + // Test with no host specified + OAuthConfig config = OAuthConfig.builder() + .appId(TEST_APP_ID) + .clientId(TEST_CLIENT_ID) + .redirectUri(TEST_REDIRECT_URI) + .build(); + OAuthHandler handler = new OAuthHandler(mockHttpClient, config); + + String authUrl = handler.authorize(); + String tokenUrl = config.getTokenEndpoint(); + + assertTrue("Auth URL should use default app host", + authUrl.contains(Util.OAUTH_APP_HOST)); + assertTrue("Token URL should use default API host", + tokenUrl.contains(Util.OAUTH_API_HOST)); + } + + @Test + public void testCustomEndpoints() { + // Test with custom endpoints + String customAuthEndpoint = "https://custom.auth.endpoint"; + String customTokenEndpoint = "https://custom.token.endpoint"; + + OAuthConfig config = OAuthConfig.builder() + .appId(TEST_APP_ID) + .clientId(TEST_CLIENT_ID) + .redirectUri(TEST_REDIRECT_URI) + .authEndpoint(customAuthEndpoint) + .tokenEndpoint(customTokenEndpoint) + .build(); + OAuthHandler handler = new OAuthHandler(mockHttpClient, config); + + String authUrl = handler.authorize(); + String tokenUrl = config.getTokenEndpoint(); + + assertEquals("Should use custom auth endpoint", + customAuthEndpoint, authUrl); + assertEquals("Should use custom token endpoint", + customTokenEndpoint, tokenUrl); + } + + + // ================= // TOKEN EXCHANGE TESTS // ================= From 2f9817923aa4ee5daa34f8e44a884d4dde5c09cf Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 9 Sep 2025 16:03:59 +0530 Subject: [PATCH 142/197] feat: Implement token storage via TokenCallback interface - Add TokenCallback interface for token storage events - Update OAuthConfig to include tokenCallback - Update OAuthHandler to use callback for token events --- .../contentstack/cms/models/OAuthConfig.java | 6 ++++++ .../contentstack/cms/oauth/OAuthHandler.java | 7 +++++++ .../contentstack/cms/oauth/TokenCallback.java | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 src/main/java/com/contentstack/cms/oauth/TokenCallback.java diff --git a/src/main/java/com/contentstack/cms/models/OAuthConfig.java b/src/main/java/com/contentstack/cms/models/OAuthConfig.java index b13119d9..0b29f02a 100644 --- a/src/main/java/com/contentstack/cms/models/OAuthConfig.java +++ b/src/main/java/com/contentstack/cms/models/OAuthConfig.java @@ -9,6 +9,7 @@ import java.util.List; import com.contentstack.cms.core.Util; +import com.contentstack.cms.oauth.TokenCallback; /** * Configuration class for OAuth 2.0 authentication @@ -27,6 +28,11 @@ public class OAuthConfig { private final String tokenEndpoint; private final String host; + /** + * Callback for token events + */ + private final TokenCallback tokenCallback; + /** * Validates the configuration * diff --git a/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java b/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java index 6e450c2c..ac79272e 100644 --- a/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java +++ b/src/main/java/com/contentstack/cms/oauth/OAuthHandler.java @@ -193,6 +193,13 @@ public CompletableFuture exchangeCodeForToken(String code) { private void _saveTokens(OAuthTokens tokens) { synchronized (tokenLock) { this.tokens = tokens; + if (config.getTokenCallback() != null) { + if (tokens != null) { + config.getTokenCallback().onTokensUpdated(tokens); + } else { + config.getTokenCallback().onTokensCleared(); + } + } } } diff --git a/src/main/java/com/contentstack/cms/oauth/TokenCallback.java b/src/main/java/com/contentstack/cms/oauth/TokenCallback.java new file mode 100644 index 00000000..64575070 --- /dev/null +++ b/src/main/java/com/contentstack/cms/oauth/TokenCallback.java @@ -0,0 +1,19 @@ +package com.contentstack.cms.oauth; + +import com.contentstack.cms.models.OAuthTokens; + +/** + * Callback interface for token events + */ +public interface TokenCallback { + /** + * Called when tokens are updated + * @param tokens The new tokens + */ + void onTokensUpdated(OAuthTokens tokens); + + /** + * Called when tokens are cleared + */ + void onTokensCleared(); +} From f9705de5f8f6b94ce8881bc8e3a598b82b14a0f9 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 9 Sep 2025 17:22:21 +0530 Subject: [PATCH 143/197] fix: Ensure default host is properly used in OAuth config --- .../com/contentstack/cms/Contentstack.java | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 09afff39..a98c6e73 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -20,6 +20,7 @@ import com.contentstack.cms.models.LoginDetails; import com.contentstack.cms.models.OAuthConfig; import com.contentstack.cms.models.OAuthTokens; +import com.contentstack.cms.oauth.TokenCallback; import com.contentstack.cms.oauth.OAuthHandler; import com.contentstack.cms.oauth.OAuthInterceptor; import com.contentstack.cms.organization.Organization; @@ -747,7 +748,20 @@ public Builder setOAuthConfig(OAuthConfig config) { * @param redirectUri Redirect URI * @return Builder instance */ + private TokenCallback tokenCallback; + + /** + * Sets the token callback for OAuth storage + * @param callback The callback to handle token storage + * @return Builder instance + */ + public Builder setTokenCallback(TokenCallback callback) { + this.tokenCallback = callback; + return this; + } + public Builder setOAuth(String appId, String clientId, String clientSecret, String redirectUri) { + // Use the builder's hostname (which defaults to Util.HOST if not set) return setOAuth(appId, clientId, clientSecret, redirectUri, this.hostname); } @@ -761,13 +775,19 @@ public Builder setOAuth(String appId, String clientId, String clientSecret, Stri * @return Builder instance */ public Builder setOAuth(String appId, String clientId, String clientSecret, String redirectUri, String host) { - this.oauthConfig = OAuthConfig.builder() + OAuthConfig.OAuthConfigBuilder builder = OAuthConfig.builder() .appId(appId) .clientId(clientId) .clientSecret(clientSecret) .redirectUri(redirectUri) - .host(host) - .build(); + .host(host); + + // Add token callback if set + if (this.tokenCallback != null) { + builder.tokenCallback(this.tokenCallback); + } + + this.oauthConfig = builder.build(); return this; } @@ -779,6 +799,7 @@ public Builder setOAuth(String appId, String clientId, String clientSecret, Stri * @return Builder instance */ public Builder setOAuthWithPKCE(String appId, String clientId, String redirectUri) { + // Use the builder's hostname (which defaults to Util.HOST if not set) return setOAuthWithPKCE(appId, clientId, redirectUri, this.hostname); } @@ -791,12 +812,18 @@ public Builder setOAuthWithPKCE(String appId, String clientId, String redirectUr * @return Builder instance */ public Builder setOAuthWithPKCE(String appId, String clientId, String redirectUri, String host) { - this.oauthConfig = OAuthConfig.builder() + OAuthConfig.OAuthConfigBuilder builder = OAuthConfig.builder() .appId(appId) .clientId(clientId) .redirectUri(redirectUri) - .host(host) - .build(); + .host(host); + + // Add token callback if set + if (this.tokenCallback != null) { + builder.tokenCallback(this.tokenCallback); + } + + this.oauthConfig = builder.build(); return this; } From 213895f032f839f490ad031b31e980c2901323b6 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 15 Sep 2025 07:14:10 +0530 Subject: [PATCH 144/197] feat: Add TOTP/MFA support for login --- pom.xml | 5 + .../com/contentstack/cms/Contentstack.java | 229 +++++++++++------- .../java/com/contentstack/cms/user/User.java | 18 ++ 3 files changed, 160 insertions(+), 92 deletions(-) diff --git a/pom.xml b/pom.xml index f2e0e305..4b788008 100644 --- a/pom.xml +++ b/pom.xml @@ -214,6 +214,11 @@ kotlin-stdlib 2.1.21 + + com.warrenstrange + googleauth + 1.5.0 + diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index a98c6e73..4041963c 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -27,6 +27,7 @@ import com.contentstack.cms.stack.Stack; import com.contentstack.cms.user.User; import com.google.gson.Gson; +import com.warrenstrange.googleauth.GoogleAuthenticator; import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; @@ -40,9 +41,8 @@ * Contentstack Java Management SDK *
* Java Management SDK Interact with the Content Management APIs and - * allow you to create, update, - * delete, and fetch content from your Contentstack account. (They are - * read-write in nature.) + * allow you to create, update, delete, and fetch content from your Contentstack + * account. (They are read-write in nature.) *
* You can use them to build your own apps and manage your content from * Contentstack. @@ -66,8 +66,7 @@ public class Contentstack { /** * All accounts registered with Contentstack are known as Users. A stack can - * have many users with varying - * permissions and roles + * have many users with varying permissions and roles *

* To perform User operations first get User instance like below. *

@@ -89,7 +88,8 @@ public class Contentstack { *
* * @return User - * @author ***REMOVED*** + * @author ***REMOVED + *** * @see User * @@ -105,15 +105,14 @@ public User user() { /** * [Note]: Before executing any calls, retrieve the authtoken by - * authenticating yourself via the Log in call of User Session. The authtoken is - * returned to the 'Response' body of - * the Log in call and is mandatory in all the calls. + * authenticating yourself via the Log in call of User Session. The + * authtoken is returned to the 'Response' body of the Log in call and is + * mandatory in all the calls. *

* Example: *

* All accounts registered with Contentstack are known as Users. A stack can - * have many users with varying - * permissions and roles + * have many users with varying permissions and roles *

* To perform User operations first get User instance like below. *

@@ -136,18 +135,20 @@ public User user() { * *
* - * @param emailId the email id of the user + * @param emailId the email id of the user * @param password the password of the user * @return LoginDetails * @throws IOException the IOException - * @author ***REMOVED*** + * @author ***REMOVED + *** * @see User * */ public Response login(String emailId, String password) throws IOException { - if (this.authtoken != null) + if (this.authtoken != null) { throw new IllegalStateException(Util.USER_ALREADY_LOGGED_IN); + } user = new User(this.instance); Response response = user.login(emailId, password).execute(); setupLoginCredentials(response); @@ -156,15 +157,14 @@ public Response login(String emailId, String password) throws IOEx /** * [Note]: Before executing any calls, retrieve the authtoken by - * authenticating yourself via the Log in call of User Session. The authtoken is - * returned to the 'Response' body of - * the Log in call and is mandatory in all the calls. + * authenticating yourself via the Log in call of User Session. The + * authtoken is returned to the 'Response' body of the Log in call and is + * mandatory in all the calls. *

* Example: *

* All accounts registered with Contentstack are known as Users. A stack can - * have many users with varying - * permissions and roles + * have many users with varying permissions and roles *

* To perform User operations first get User instance like below. *

@@ -187,13 +187,14 @@ public Response login(String emailId, String password) throws IOEx * *
* - * @param emailId the email id + * @param emailId the email id * @param password the password * @param tfaToken the tfa token * @return LoginDetails * @throws IOException the io exception * @throws IOException the IOException - * @author ***REMOVED*** + * @author ***REMOVED + *** * @see Login @@ -201,14 +202,52 @@ public Response login(String emailId, String password) throws IOEx * */ public Response login(String emailId, String password, String tfaToken) throws IOException { - if (this.authtoken != null) + if (this.authtoken != null) { throw new IllegalStateException(Util.USER_ALREADY_LOGGED_IN); + } user = new User(this.instance); Response response = user.login(emailId, password, tfaToken).execute(); setupLoginCredentials(response); user = new User(this.instance); return response; } + /** + * Login with TOTP/MFA support. + * + * @param emailId The email ID of the user + * @param password The user's password + * @param tfaToken The TOTP token (can be null if mfaSecret is provided) + * @param mfaSecret The MFA secret key to generate TOTP (optional if tfaToken is provided) + * @return Response containing login details + * @throws IOException if there's a network error + * @throws IllegalArgumentException if both tfaToken and mfaSecret are null or if secret is invalid + * @throws IllegalStateException if user is already logged in + */ + public Response login(String emailId, String password, String tfaToken, String mfaSecret) throws IOException { + String finalTfaToken = tfaToken; + if (mfaSecret != null && !mfaSecret.isEmpty()) { + finalTfaToken = generateTOTP(mfaSecret); + } + if (this.authtoken != null) { + throw new IllegalStateException(Util.USER_ALREADY_LOGGED_IN); + } + if (finalTfaToken == null) { + throw new IllegalArgumentException("Either tfaToken or mfaSecret must be provided"); + } + user = new User(this.instance); + Response response = user.login(emailId, password, finalTfaToken).execute(); + setupLoginCredentials(response); + return response; + } + + private String generateTOTP(String secret) { + try { + GoogleAuthenticator gAuth = new GoogleAuthenticator(); + return String.format("%06d", gAuth.getTotpPassword(secret)); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid MFA secret key", e); + } + } private void setupLoginCredentials(Response loginResponse) throws IOException { if (loginResponse.isSuccessful()) { @@ -230,8 +269,8 @@ private void setupLoginCredentials(Response loginResponse) throws } /** - * The Log out of your account call is used to sign out the user of Contentstack - * account + * The Log out of your account call is used to sign out the user of + * Contentstack account *

* Example * @@ -250,8 +289,8 @@ Response logout() throws IOException { } /** - * The Log out of your account using authtoken is used to sign out the user of - * Contentstack account + * The Log out of your account using authtoken is used to sign out the user + * of Contentstack account *

* Example * @@ -276,9 +315,8 @@ Response logoutWithAuthtoken(String authtoken) throws IOException /** * Organization is the top-level entity in the hierarchy of Contentstack, - * consisting of stacks and stack resources, - * and users. Organization allows easy management of projects as well as users - * within the Organization. + * consisting of stacks and stack resources, and users. Organization allows + * easy management of projects as well as users within the Organization. *

* Example * @@ -293,7 +331,7 @@ public Organization organization() { if (!isOAuthConfigured() && this.authtoken == null) { throw new IllegalStateException(Util.OAUTH_LOGIN_REQUIRED + " organization"); } - + // If using OAuth, get organization from tokens if (isOAuthConfigured() && oauthHandler.getTokens() != null) { String orgUid = oauthHandler.getTokens().getOrganizationUid(); @@ -301,19 +339,19 @@ public Organization organization() { return organization(orgUid); } } - + return new Organization(this.instance); } /** * Organization is the top-level entity in the hierarchy of Contentstack, - * consisting of stacks and stack resources, - * and users. Organization allows easy management of projects as well as users - * within the Organization. + * consisting of stacks and stack resources, and users. Organization allows + * easy management of projects as well as users within the Organization. *

* Example * - * @param organizationUid The UID of the organization that you want to retrieve + * @param organizationUid The UID of the organization that you want to + * retrieve * @return the organization *
* Example @@ -322,7 +360,7 @@ public Organization organization() { * Contentstack contentstack = new Contentstack.Builder().build(); *
* Organization org = contentstack.organization(); - *

+ *
*/ public Organization organization(@NotNull String organizationUid) { if (!isOAuthConfigured() && this.authtoken == null) { @@ -337,10 +375,9 @@ public Organization organization(@NotNull String organizationUid) { /** * stack - * A stack is - * a space that stores the content of a project (a web or mobile property). - * Within a stack, you can create content - * structures, content entries, users, etc. related to the project + * A stack is a space that stores the content of a project (a web or mobile + * property). Within a stack, you can create content structures, content + * entries, users, etc. related to the project *

* Example * @@ -361,10 +398,9 @@ public Stack stack() { /** * stack - * A stack is - * a space that stores the content of a project (a web or mobile property). - * Within a stack, you can create content - * structures, content entries, users, etc. related to the project + * A stack is a space that stores the content of a project (a web or mobile + * property). Within a stack, you can create content structures, content + * entries, users, etc. related to the project *

* Example * @@ -386,10 +422,9 @@ public Stack stack(@NotNull Map header) { /** * stack - * A stack is - * a space that stores the content of a project (a web or mobile property). - * Within a stack, you can create content - * structures, content entries, users, etc. related to the project + * A stack is a space that stores the content of a project (a web or mobile + * property). Within a stack, you can create content structures, content + * entries, users, etc. related to the project *

* Example * @@ -399,7 +434,7 @@ public Stack stack(@NotNull Map header) { *

* * @param managementToken the authorization for the stack - * @param apiKey the apiKey for the stack + * @param apiKey the apiKey for the stack * @return the stack instance */ public Stack stack(@NotNull String apiKey, @NotNull String managementToken) { @@ -412,10 +447,9 @@ public Stack stack(@NotNull String apiKey, @NotNull String managementToken) { /** * stack - * A stack is - * a space that stores the content of a project (a web or mobile property). - * Within a stack, you can create content - * structures, content entries, users, etc. related to the project + * A stack is a space that stores the content of a project (a web or mobile + * property). Within a stack, you can create content structures, content + * entries, users, etc. related to the project *

* Example * @@ -442,10 +476,9 @@ public Stack stack(@NotNull String key) { /** * stack - * A stack is - * a space that stores the content of a project (a web or mobile property). - * Within a stack, you can create content - * structures, content entries, users, etc. related to the project + * A stack is a space that stores the content of a project (a web or mobile + * property). Within a stack, you can create content structures, content + * entries, users, etc. related to the project *

* Example * @@ -455,8 +488,8 @@ public Stack stack(@NotNull String key) { *

* * @param managementToken the authorization for the stack - * @param apiKey the apiKey for the stack - * @param branch the branch that include branching in the response + * @param apiKey the apiKey for the stack + * @param branch the branch that include branching in the response * @return the stack instance */ public Stack stack(@NotNull String apiKey, @NotNull String managementToken, @NotNull String branch) { @@ -469,6 +502,7 @@ public Stack stack(@NotNull String apiKey, @NotNull String managementToken, @Not /** * Get the OAuth authorization URL for the user to visit + * * @return Authorization URL string * @throws IllegalStateException if OAuth is not configured */ @@ -481,6 +515,7 @@ public String getOAuthAuthorizationUrl() { /** * Exchange OAuth authorization code for tokens + * * @param code Authorization code from OAuth callback * @return CompletableFuture containing OAuth tokens * @throws IllegalStateException if OAuth is not configured @@ -494,8 +529,10 @@ public CompletableFuture exchangeOAuthCode(String code) { /** * Refresh the OAuth access token + * * @return CompletableFuture containing new OAuth tokens - * @throws IllegalStateException if OAuth is not configured or no refresh token available + * @throws IllegalStateException if OAuth is not configured or no refresh + * token available */ public CompletableFuture refreshOAuthToken() { if (!isOAuthConfigured()) { @@ -506,6 +543,7 @@ public CompletableFuture refreshOAuthToken() { /** * Get the current OAuth tokens + * * @return Current OAuth tokens or null if not available */ public OAuthTokens getOAuthTokens() { @@ -514,6 +552,7 @@ public OAuthTokens getOAuthTokens() { /** * Check if we have valid OAuth tokens + * * @return true if we have valid tokens */ public boolean hasValidOAuthTokens() { @@ -522,6 +561,7 @@ public boolean hasValidOAuthTokens() { /** * Check if OAuth is configured + * * @return true if OAuth is configured */ public boolean isOAuthConfigured() { @@ -530,6 +570,7 @@ public boolean isOAuthConfigured() { /** * Get the OAuth handler instance + * * @return OAuth handler or null if not configured */ public OAuthHandler getOAuthHandler() { @@ -538,6 +579,7 @@ public OAuthHandler getOAuthHandler() { /** * Logout from OAuth session and optionally revoke authorization + * * @param revokeAuthorization If true, revokes the OAuth authorization * @return CompletableFuture that completes when logout is done */ @@ -550,6 +592,7 @@ public CompletableFuture oauthLogout(boolean revokeAuthorization) { /** * Logout from OAuth session without revoking authorization + * * @return CompletableFuture that completes when logout is done */ public CompletableFuture oauthLogout() { @@ -595,8 +638,8 @@ public static class Builder { private Boolean retry = Util.RETRY_ON_FAILURE;// Default base url for contentstack /** - * Default ConnectionPool holds up to 5 idle connections which - * will be evicted after 5 minutes of inactivity. + * Default ConnectionPool holds up to 5 idle connections which will be + * evicted after 5 minutes of inactivity. */ private ConnectionPool connectionPool = new ConnectionPool(); // Connection @@ -609,8 +652,7 @@ public Builder() { /** * Sets proxy. (Setting proxy to the OkHttpClient) Proxy = new - * Proxy(Proxy.Type.HTTP, new - * InetSocketAddress(proxyHost, proxyPort)); + * Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); *
*

* {@code @@ -687,26 +729,21 @@ public Builder setTimeout(int timeout) { /** * Create a new connection pool with tuning parameters appropriate for a - * single-user application. - * The tuning parameters in this pool are subject to change in future OkHttp - * releases. Currently, - * this pool holds up to 5 idle connections which will be evicted after 5 - * minutes of inactivity. + * single-user application. The tuning parameters in this pool are + * subject to change in future OkHttp releases. Currently, this pool + * holds up to 5 idle connections which will be evicted after 5 minutes + * of inactivity. *

*

- * public ConnectionPool() { - * this(5, 5, TimeUnit.MINUTES); - * } + * public ConnectionPool() { this(5, 5, TimeUnit.MINUTES); } * * @param maxIdleConnections Maximum number of idle connections - * @param keepAliveDuration The Keep Alive Duration - * @param timeUnit A TimeUnit represents time durations at a given - * unit of granularity and provides utility methods to - * convert across units + * @param keepAliveDuration The Keep Alive Duration + * @param timeUnit A TimeUnit represents time durations at a given unit + * of granularity and provides utility methods to convert across units * @return instance of Builder *

- * Example: - * {@code + * Example: {@code * Contentstack cs = new Contentstack.Builder() * .setAuthtoken(AUTHTOKEN) * .setConnectionPool(5, 400, TimeUnit.MILLISECONDS) @@ -732,6 +769,7 @@ public Builder setAuthtoken(String authtoken) { /** * Sets OAuth configuration for the client + * * @param config OAuth configuration * @return Builder instance */ @@ -742,6 +780,7 @@ public Builder setOAuthConfig(OAuthConfig config) { /** * Configures OAuth with client credentials (traditional flow) + * * @param appId Application ID * @param clientId Client ID * @param clientSecret Client secret @@ -752,6 +791,7 @@ public Builder setOAuthConfig(OAuthConfig config) { /** * Sets the token callback for OAuth storage + * * @param callback The callback to handle token storage * @return Builder instance */ @@ -767,20 +807,22 @@ public Builder setOAuth(String appId, String clientId, String clientSecret, Stri /** * Configures OAuth with client credentials and specific host + * * @param appId Application ID * @param clientId Client ID * @param clientSecret Client secret * @param redirectUri Redirect URI - * @param host API host (e.g. "api.contentstack.io", "eu-api.contentstack.com") + * @param host API host (e.g. "api.contentstack.io", + * "eu-api.contentstack.com") * @return Builder instance */ public Builder setOAuth(String appId, String clientId, String clientSecret, String redirectUri, String host) { OAuthConfig.OAuthConfigBuilder builder = OAuthConfig.builder() - .appId(appId) - .clientId(clientId) - .clientSecret(clientSecret) - .redirectUri(redirectUri) - .host(host); + .appId(appId) + .clientId(clientId) + .clientSecret(clientSecret) + .redirectUri(redirectUri) + .host(host); // Add token callback if set if (this.tokenCallback != null) { @@ -793,6 +835,7 @@ public Builder setOAuth(String appId, String clientId, String clientSecret, Stri /** * Configures OAuth with PKCE (no client secret) + * * @param appId Application ID * @param clientId Client ID * @param redirectUri Redirect URI @@ -805,18 +848,20 @@ public Builder setOAuthWithPKCE(String appId, String clientId, String redirectUr /** * Configures OAuth with PKCE (no client secret) and specific host + * * @param appId Application ID * @param clientId Client ID * @param redirectUri Redirect URI - * @param host API host (e.g. "api.contentstack.io", "eu-api.contentstack.com") + * @param host API host (e.g. "api.contentstack.io", + * "eu-api.contentstack.com") * @return Builder instance */ public Builder setOAuthWithPKCE(String appId, String clientId, String redirectUri, String host) { OAuthConfig.OAuthConfigBuilder builder = OAuthConfig.builder() - .appId(appId) - .clientId(clientId) - .redirectUri(redirectUri) - .host(host); + .appId(appId) + .clientId(clientId) + .redirectUri(redirectUri) + .host(host); // Add token callback if set if (this.tokenCallback != null) { @@ -849,7 +894,7 @@ private void validateClient(Contentstack contentstack) { .addConverterFactory(GsonConverterFactory.create()) .client(httpClient(contentstack, this.retry)).build(); contentstack.instance = this.instance; - + // Initialize OAuth if configured if (this.oauthConfig != null) { // OAuth handler and interceptor are created in httpClient @@ -872,12 +917,12 @@ private OkHttpClient httpClient(Contentstack contentstack, Boolean retryOnFailur OkHttpClient tempClient = builder.build(); this.oauthHandler = new OAuthHandler(tempClient, this.oauthConfig); this.oauthInterceptor = new OAuthInterceptor(this.oauthHandler); - + // Configure early access if needed if (this.earlyAccess != null) { this.oauthInterceptor.setEarlyAccess(this.earlyAccess); } - + // Add interceptor to handle OAuth, token refresh, and retries builder.addInterceptor(this.oauthInterceptor); } else { diff --git a/src/main/java/com/contentstack/cms/user/User.java b/src/main/java/com/contentstack/cms/user/User.java index 53b05b8f..cc360672 100644 --- a/src/main/java/com/contentstack/cms/user/User.java +++ b/src/main/java/com/contentstack/cms/user/User.java @@ -8,6 +8,7 @@ import org.json.simple.JSONObject; import retrofit2.Call; import retrofit2.Retrofit; +import com.warrenstrange.googleauth.GoogleAuthenticator; import java.util.HashMap; @@ -103,6 +104,23 @@ private HashMap setCredentials(@NotNull String... arguments) { return credentials; } + public Call login(@NotNull String email, @NotNull String password, @NotNull String tfaToken, String mfaSecret) { + String finalTfaToken = tfaToken; + if (mfaSecret != null && !mfaSecret.isEmpty()) { + finalTfaToken = generateTOTP(mfaSecret); + } + return login(email, password, finalTfaToken); + } + + private String generateTOTP(String secret) { + try { + GoogleAuthenticator gAuth = new GoogleAuthenticator(); + return String.format("%06d", gAuth.getTotpPassword(secret)); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid MFA secret key", e); + } + } + /** * The Get user call returns comprehensive information of an existing user * account. The information returned From 4484d7b262532c4a7bea2514915881d1d6267fc6 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 15 Sep 2025 07:58:02 +0530 Subject: [PATCH 145/197] refactor: Enhance login functionality with TOTP/MFA support - Simplified login method to handle TOTP and MFA secret. - Added input validation for email, password, and TOTP token. - Updated User class to implement generic BaseImplementation. - Introduced unit tests for login scenarios including TOTP and MFA handling. --- .../com/contentstack/cms/Contentstack.java | 96 +++++---------- .../java/com/contentstack/cms/user/User.java | 66 +++++++++-- .../java/com/contentstack/cms/TestClient.java | 1 + .../cms/models/LoginDetailTest.java | 1 + .../contentstack/cms/user/UserUnitTests.java | 111 ++++++++++++++++++ 5 files changed, 198 insertions(+), 77 deletions(-) diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 4041963c..93beae95 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -156,61 +156,19 @@ public Response login(String emailId, String password) throws IOEx } /** - * [Note]: Before executing any calls, retrieve the authtoken by - * authenticating yourself via the Log in call of User Session. The - * authtoken is returned to the 'Response' body of the Log in call and is - * mandatory in all the calls. - *

- * Example: - *

- * All accounts registered with Contentstack are known as Users. A stack can - * have many users with varying permissions and roles - *

- * To perform User operations first get User instance like below. - *

- * Example: + * Login with TOTP token. * - *

-     * Contentstack contentstack = new Contentstack.Builder().setAuthtoken("authtoken").build();
-     * Response login = contentstack.login();
-     *
-     * Access more other user functions from the userInstance
-     * 
- * - *
- * OR: - * - *
-     * Contentstack contentstack = new Contentstack.Builder().build();
-     * Response login = contentstack.login("emailId", "password");
-     * 
- * - *
- * - * @param emailId the email id - * @param password the password - * @param tfaToken the tfa token - * @return LoginDetails - * @throws IOException the io exception - * @throws IOException the IOException - * @author ***REMOVED - *** - * @see Login - * your account - * + * @param emailId The email ID of the user + * @param password The user's password + * @param tfaToken The TOTP token + * @return Response containing login details + * @throws IOException if there's a network error + * @throws IllegalStateException if user is already logged in */ public Response login(String emailId, String password, String tfaToken) throws IOException { - if (this.authtoken != null) { - throw new IllegalStateException(Util.USER_ALREADY_LOGGED_IN); - } - user = new User(this.instance); - Response response = user.login(emailId, password, tfaToken).execute(); - setupLoginCredentials(response); - user = new User(this.instance); - return response; + return login(emailId, password, tfaToken, null); } + /** * Login with TOTP/MFA support. * @@ -224,30 +182,40 @@ public Response login(String emailId, String password, String tfaT * @throws IllegalStateException if user is already logged in */ public Response login(String emailId, String password, String tfaToken, String mfaSecret) throws IOException { - String finalTfaToken = tfaToken; - if (mfaSecret != null && !mfaSecret.isEmpty()) { - finalTfaToken = generateTOTP(mfaSecret); - } + // Check if already logged in if (this.authtoken != null) { throw new IllegalStateException(Util.USER_ALREADY_LOGGED_IN); } - if (finalTfaToken == null) { + + // Validate inputs + if (emailId == null || emailId.trim().isEmpty()) { + throw new IllegalArgumentException("Email is required"); + } + if (password == null || password.trim().isEmpty()) { + throw new IllegalArgumentException("Password is required"); + } + if ((tfaToken == null || tfaToken.trim().isEmpty()) && (mfaSecret == null || mfaSecret.trim().isEmpty())) { throw new IllegalArgumentException("Either tfaToken or mfaSecret must be provided"); } + + // Generate TOTP if needed + String finalTfaToken = tfaToken; + if ((tfaToken == null || tfaToken.trim().isEmpty()) && mfaSecret != null && !mfaSecret.trim().isEmpty()) { + try { + GoogleAuthenticator gAuth = new GoogleAuthenticator(); + finalTfaToken = String.format("%06d", gAuth.getTotpPassword(mfaSecret)); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to generate TOTP token: " + e.getMessage(), e); + } + } + + // Perform login user = new User(this.instance); Response response = user.login(emailId, password, finalTfaToken).execute(); setupLoginCredentials(response); return response; } - private String generateTOTP(String secret) { - try { - GoogleAuthenticator gAuth = new GoogleAuthenticator(); - return String.format("%06d", gAuth.getTotpPassword(secret)); - } catch (Exception e) { - throw new IllegalArgumentException("Invalid MFA secret key", e); - } - } private void setupLoginCredentials(Response loginResponse) throws IOException { if (loginResponse.isSuccessful()) { diff --git a/src/main/java/com/contentstack/cms/user/User.java b/src/main/java/com/contentstack/cms/user/User.java index cc360672..d8839468 100644 --- a/src/main/java/com/contentstack/cms/user/User.java +++ b/src/main/java/com/contentstack/cms/user/User.java @@ -24,7 +24,7 @@ * @version v0.1.0 * @since 2022-10-22 */ -public class User implements BaseImplementation { +public class User implements BaseImplementation { protected final UserService userService; protected HashMap headers; @@ -80,14 +80,18 @@ private HashMap loginHeader() { } /** - * Login call. + * Login with TOTP token for two-factor authentication. * * @param email email for user to login * @param password password for user to login - * @param tfaToken the tfa token - * @return Call + * @param tfaToken the TOTP token for two-factor authentication + * @return Call containing login details + * @throws IllegalArgumentException if email, password, or tfaToken is null or empty */ - public Call login(@NotNull String email, @NotNull String password, @NotNull String tfaToken) { + public Call login(@NotNull String email, @NotNull String password, String tfaToken) { + if (tfaToken == null || tfaToken.trim().isEmpty()) { + throw new IllegalArgumentException("TOTP token cannot be null or empty"); + } HashMap> userSession = new HashMap<>(); userSession.put("user", setCredentials(email, password, tfaToken)); JSONObject userDetail = new JSONObject(userSession); @@ -95,29 +99,66 @@ public Call login(@NotNull String email, @NotNull String password, } private HashMap setCredentials(@NotNull String... arguments) { + if (arguments == null || arguments.length < 2) { + throw new IllegalArgumentException("Email and password are required"); + } + if (arguments[0] == null || arguments[0].trim().isEmpty()) { + throw new IllegalArgumentException("Email cannot be null or empty"); + } + if (arguments[1] == null || arguments[1].trim().isEmpty()) { + throw new IllegalArgumentException("Password cannot be null or empty"); + } + HashMap credentials = new HashMap<>(); credentials.put("email", arguments[0]); credentials.put("password", arguments[1]); - if (arguments.length > 2) { + + if (arguments.length > 2 && arguments[2] != null && !arguments[2].trim().isEmpty()) { credentials.put("tfa_token", arguments[2]); } return credentials; } - public Call login(@NotNull String email, @NotNull String password, @NotNull String tfaToken, String mfaSecret) { + public Call login(@NotNull String email, @NotNull String password, String tfaToken, String mfaSecret) { + if (email.trim().isEmpty()) { + throw new IllegalArgumentException("Email cannot be empty"); + } + if (password.trim().isEmpty()) { + throw new IllegalArgumentException("Password cannot be empty"); + } + if ((tfaToken == null || tfaToken.trim().isEmpty()) && (mfaSecret == null || mfaSecret.trim().isEmpty())) { + throw new IllegalArgumentException("Either tfaToken or mfaSecret must be provided"); + } + String finalTfaToken = tfaToken; - if (mfaSecret != null && !mfaSecret.isEmpty()) { - finalTfaToken = generateTOTP(mfaSecret); + if ((tfaToken == null || tfaToken.trim().isEmpty()) && mfaSecret != null && !mfaSecret.trim().isEmpty()) { + try { + finalTfaToken = generateTOTP(mfaSecret); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Failed to generate TOTP token: " + e.getMessage(), e); + } + } + if (finalTfaToken == null || finalTfaToken.trim().isEmpty()) { + throw new IllegalArgumentException("Failed to generate valid TOTP token"); } return login(email, password, finalTfaToken); } private String generateTOTP(String secret) { + if (secret == null || secret.trim().isEmpty()) { + throw new IllegalArgumentException("MFA secret cannot be null or empty"); + } try { GoogleAuthenticator gAuth = new GoogleAuthenticator(); - return String.format("%06d", gAuth.getTotpPassword(secret)); + String totp = String.format("%06d", gAuth.getTotpPassword(secret)); + if (!totp.matches("\\d{6}")) { + throw new IllegalArgumentException("Generated TOTP token is invalid"); + } + return totp; + } catch (IllegalArgumentException e) { + throw e; } catch (Exception e) { - throw new IllegalArgumentException("Invalid MFA secret key", e); + throw new IllegalArgumentException("Invalid MFA secret key: " + e.getMessage(), e); } } @@ -146,8 +187,7 @@ public Call getUser() { * @return Call */ public Call update(JSONObject body) { - HashMap headers = new HashMap<>(); - return userService.update(headers, body); + return userService.update(new HashMap<>(), body); } /** diff --git a/src/test/java/com/contentstack/cms/TestClient.java b/src/test/java/com/contentstack/cms/TestClient.java index 37444610..74b545d9 100644 --- a/src/test/java/com/contentstack/cms/TestClient.java +++ b/src/test/java/com/contentstack/cms/TestClient.java @@ -17,6 +17,7 @@ public class TestClient { public final static String USER_ID = (env.get("userId") != null) ? env.get("userId") : "c11e668e0295477f"; public final static String OWNERSHIP = (env.get("ownershipToken") != null) ? env.get("ownershipToken") : "ownershipTokenId"; + // file deepcode ignore NonCryptoHardcodedSecret/test: public final static String API_KEY = (env.get("apiKey") != null) ? env.get("apiKey") : "apiKey99999999"; public final static String MANAGEMENT_TOKEN = (env.get("managementToken") != null) ? env.get("managementToken") : "managementToken99999999"; diff --git a/src/test/java/com/contentstack/cms/models/LoginDetailTest.java b/src/test/java/com/contentstack/cms/models/LoginDetailTest.java index b73ec23f..5e070556 100644 --- a/src/test/java/com/contentstack/cms/models/LoginDetailTest.java +++ b/src/test/java/com/contentstack/cms/models/LoginDetailTest.java @@ -102,6 +102,7 @@ void getterSetterUserModelLastName() { @Test void getterSetterUserModelUsername() { UserModel userModel = new UserModel(); + // deepcode ignore NoHardcodedCredentials/test: userModel.setUsername("***REMOVED***"); Assertions.assertEquals("***REMOVED***", userModel.getUsername()); diff --git a/src/test/java/com/contentstack/cms/user/UserUnitTests.java b/src/test/java/com/contentstack/cms/user/UserUnitTests.java index 685c4bd9..3d5484d2 100644 --- a/src/test/java/com/contentstack/cms/user/UserUnitTests.java +++ b/src/test/java/com/contentstack/cms/user/UserUnitTests.java @@ -596,4 +596,115 @@ void testUserBaseParameters() { Assertions.assertEquals(0, requestInfo.url().queryParameterNames().size()); } + + @Test + @Order(42) + @DisplayName("Test login with TOTP token") + void testLoginWithTOTPToken() { + // Test basic login with TOTP token + Request requestInfo = userInstance.login("test@example.com", "password", "123456").request(); + + // Verify request format + Assertions.assertEquals("POST", requestInfo.method()); + Assertions.assertEquals("/v3/user-session", requestInfo.url().encodedPath()); + + // Verify request body + String requestBody = requestInfo.body().toString(); + Assertions.assertTrue(requestBody.contains("\"tfa_token\":\"123456\"")); + } + + @Test + @Order(43) + @DisplayName("Test login with MFA parameters") + void testLoginWithMFAParameters() { + // Test login with both token and secret (token should be used) + Request requestInfo = userInstance.login("test@example.com", "password", "123456", "test-secret").request(); + + // Verify request format + Assertions.assertEquals("POST", requestInfo.method()); + Assertions.assertEquals("/v3/user-session", requestInfo.url().encodedPath()); + + // Verify request body uses token + String requestBody = requestInfo.body().toString(); + Assertions.assertTrue(requestBody.contains("\"tfa_token\":\"123456\"")); + } + + @Test + @Order(44) + @DisplayName("Test login validation") + void testLoginValidation() { + // Test with missing required parameters + Assertions.assertThrows(IllegalArgumentException.class, + () -> userInstance.login(null, "password", "123456").request()); + + Assertions.assertThrows(IllegalArgumentException.class, + () -> userInstance.login("test@example.com", null, "123456").request()); + + Assertions.assertThrows(IllegalArgumentException.class, + () -> userInstance.login("test@example.com", "password", null).request()); + } + + @Test + @Order(45) + @DisplayName("Test TOTP generation from MFA secret") + void testTOTPGenerationFromSecret() { + // Test login with MFA secret only + Request requestInfo = userInstance.login("test@example.com", "password", null, "test-secret").request(); + + // Verify request format + Assertions.assertEquals("POST", requestInfo.method()); + Assertions.assertEquals("/v3/user-session", requestInfo.url().encodedPath()); + + // Verify generated TOTP format + String requestBody = requestInfo.body().toString(); + Assertions.assertTrue(requestBody.contains("\"tfa_token\":\"")); + + // Extract TOTP token and verify it's 6 digits + String token = requestBody.split("\"tfa_token\":\"")[1].split("\"")[0]; + Assertions.assertTrue(token.matches("\\d{6}"), "TOTP should be 6 digits"); + } + + @Test + @Order(46) + @DisplayName("Test invalid MFA secret handling") + void testInvalidMFASecret() { + // Test with invalid MFA secret + IllegalArgumentException exception = Assertions.assertThrows( + IllegalArgumentException.class, + () -> userInstance.login("test@example.com", "password", null, "invalid-secret").request() + ); + + Assertions.assertTrue(exception.getMessage().contains("Invalid MFA secret key")); + } + + @Test + @Order(47) + @DisplayName("Test TOTP token priority over MFA secret") + void testTOTPPriority() { + // When both token and secret are provided, token should be used + Request requestInfo = userInstance.login("test@example.com", "password", "123456", "test-secret").request(); + String requestBody = requestInfo.body().toString(); + + // Should use provided token, not generate from secret + Assertions.assertTrue(requestBody.contains("\"tfa_token\":\"123456\"")); + } + + @Test + @Order(48) + @DisplayName("Test null/empty MFA parameters") + void testNullEmptyMFAParams() { + // Test with both null + IllegalArgumentException exception1 = Assertions.assertThrows( + IllegalArgumentException.class, + () -> userInstance.login("test@example.com", "password", null, null).request() + ); + Assertions.assertTrue(exception1.getMessage().contains("Either tfaToken or mfaSecret must be provided")); + + // Test with empty secret + IllegalArgumentException exception2 = Assertions.assertThrows( + IllegalArgumentException.class, + () -> userInstance.login("test@example.com", "password", null, "").request() + ); + Assertions.assertTrue(exception2.getMessage().contains("Either tfaToken or mfaSecret must be provided")); + } } From 1e1dd7cb62b21dab97aa55c1f418ddd9796ac92e Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 15 Sep 2025 08:20:56 +0530 Subject: [PATCH 146/197] refactor: Simplify OAuth configuration methods and enhance PKCE flow support - Removed deprecated setOAuthWithPKCE methods and consolidated OAuth configuration into a single setOAuth method. - Updated documentation to clarify clientSecret usage for PKCE flow. - Adjusted error message for missing OAuth configuration to reflect new method usage. --- .../com/contentstack/cms/Contentstack.java | 56 ++++--------------- .../java/com/contentstack/cms/core/Util.java | 2 +- .../com/contentstack/cms/oauth/OAuthTest.java | 6 +- 3 files changed, 16 insertions(+), 48 deletions(-) diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index a98c6e73..c42f357a 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -740,14 +740,6 @@ public Builder setOAuthConfig(OAuthConfig config) { return this; } - /** - * Configures OAuth with client credentials (traditional flow) - * @param appId Application ID - * @param clientId Client ID - * @param clientSecret Client secret - * @param redirectUri Redirect URI - * @return Builder instance - */ private TokenCallback tokenCallback; /** @@ -760,64 +752,40 @@ public Builder setTokenCallback(TokenCallback callback) { return this; } - public Builder setOAuth(String appId, String clientId, String clientSecret, String redirectUri) { - // Use the builder's hostname (which defaults to Util.HOST if not set) - return setOAuth(appId, clientId, clientSecret, redirectUri, this.hostname); - } - /** - * Configures OAuth with client credentials and specific host + * Configures OAuth authentication. PKCE flow is automatically used when clientSecret is null or empty. * @param appId Application ID * @param clientId Client ID - * @param clientSecret Client secret + * @param clientSecret Client secret (optional). If null or empty, PKCE flow will be used * @param redirectUri Redirect URI - * @param host API host (e.g. "api.contentstack.io", "eu-api.contentstack.com") * @return Builder instance */ - public Builder setOAuth(String appId, String clientId, String clientSecret, String redirectUri, String host) { - OAuthConfig.OAuthConfigBuilder builder = OAuthConfig.builder() - .appId(appId) - .clientId(clientId) - .clientSecret(clientSecret) - .redirectUri(redirectUri) - .host(host); - - // Add token callback if set - if (this.tokenCallback != null) { - builder.tokenCallback(this.tokenCallback); - } - - this.oauthConfig = builder.build(); - return this; - } - - /** - * Configures OAuth with PKCE (no client secret) - * @param appId Application ID - * @param clientId Client ID - * @param redirectUri Redirect URI - * @return Builder instance - */ - public Builder setOAuthWithPKCE(String appId, String clientId, String redirectUri) { + public Builder setOAuth(String appId, String clientId, String clientSecret, String redirectUri) { // Use the builder's hostname (which defaults to Util.HOST if not set) - return setOAuthWithPKCE(appId, clientId, redirectUri, this.hostname); + return setOAuth(appId, clientId, clientSecret, redirectUri, this.hostname); } /** - * Configures OAuth with PKCE (no client secret) and specific host + * Configures OAuth authentication with a specific host. PKCE flow is automatically used when clientSecret is null or empty. * @param appId Application ID * @param clientId Client ID + * @param clientSecret Client secret (optional). If null or empty, PKCE flow will be used * @param redirectUri Redirect URI * @param host API host (e.g. "api.contentstack.io", "eu-api.contentstack.com") * @return Builder instance */ - public Builder setOAuthWithPKCE(String appId, String clientId, String redirectUri, String host) { + public Builder setOAuth(String appId, String clientId, String clientSecret, String redirectUri, String host) { OAuthConfig.OAuthConfigBuilder builder = OAuthConfig.builder() .appId(appId) .clientId(clientId) .redirectUri(redirectUri) .host(host); + // Only set clientSecret if provided (otherwise PKCE flow will be used) + if (clientSecret != null && !clientSecret.trim().isEmpty()) { + builder.clientSecret(clientSecret); + } + // Add token callback if set if (this.tokenCallback != null) { builder.tokenCallback(this.tokenCallback); diff --git a/src/main/java/com/contentstack/cms/core/Util.java b/src/main/java/com/contentstack/cms/core/Util.java index 11f36939..a07b302e 100644 --- a/src/main/java/com/contentstack/cms/core/Util.java +++ b/src/main/java/com/contentstack/cms/core/Util.java @@ -61,7 +61,7 @@ public class Util { public static final String OAUTH_NO_TOKENS = "No OAuth tokens available. Please authenticate first."; public static final String OAUTH_NO_REFRESH_TOKEN = "No refresh token available"; public static final String OAUTH_EMPTY_CODE = "Authorization code cannot be null or empty"; - public static final String OAUTH_CONFIG_MISSING = "OAuth is not configured. Use Builder.setOAuth() or Builder.setOAuthWithPKCE()"; + public static final String OAUTH_CONFIG_MISSING = "OAuth is not configured. Use Builder.setOAuth() with or without clientSecret for PKCE flow"; public static final String OAUTH_REFRESH_FAILED = "Failed to refresh access token"; public static final String OAUTH_REVOKE_FAILED = "Failed to revoke authorization"; public static final String OAUTH_STATUS_FAILED = "Failed to get authorization status"; diff --git a/src/test/java/com/contentstack/cms/oauth/OAuthTest.java b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java index 245d6db4..660c9176 100644 --- a/src/test/java/com/contentstack/cms/oauth/OAuthTest.java +++ b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java @@ -80,7 +80,7 @@ public void setup() { // Create Contentstack clients pkceClient = new Contentstack.Builder() - .setOAuthWithPKCE(TEST_APP_ID, TEST_CLIENT_ID, TEST_REDIRECT_URI) + .setOAuth(TEST_APP_ID, TEST_CLIENT_ID, null, TEST_REDIRECT_URI) .build(); clientSecretClient = new Contentstack.Builder() @@ -113,7 +113,7 @@ public void testInvalidConfigurations() { // Test invalid app ID try { new Contentstack.Builder() - .setOAuthWithPKCE("", TEST_CLIENT_ID, TEST_REDIRECT_URI) + .setOAuth("", TEST_CLIENT_ID, null, TEST_REDIRECT_URI) .build(); fail("Should throw exception for empty app ID"); } catch (IllegalArgumentException e) { @@ -291,7 +291,7 @@ public void testHostStorage() { // Test host storage via PKCE builder client = new Contentstack.Builder() - .setOAuthWithPKCE(TEST_APP_ID, TEST_CLIENT_ID, TEST_REDIRECT_URI, testHost) + .setOAuth(TEST_APP_ID, TEST_CLIENT_ID, null, TEST_REDIRECT_URI, testHost) .build(); authUrl = client.getOAuthAuthorizationUrl(); From c4e33a689d47185bd75a6f28909549a6666105c2 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 15 Sep 2025 08:51:25 +0530 Subject: [PATCH 147/197] refactor: Update OAuth configuration methods to enhance clarity and support for optional client secret - Simplified setOAuth methods by removing the clientSecret parameter from the primary overloads. - Added a new setOAuth method to accommodate an optional client secret while maintaining PKCE flow. - Updated documentation to reflect changes in method signatures and clarify the use of client secret. --- .../com/contentstack/cms/Contentstack.java | 25 +++++++++++++------ .../com/contentstack/cms/oauth/OAuthTest.java | 10 ++++---- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index c42f357a..dbf3dbc0 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -753,28 +753,39 @@ public Builder setTokenCallback(TokenCallback callback) { } /** - * Configures OAuth authentication. PKCE flow is automatically used when clientSecret is null or empty. + * Configures OAuth authentication with PKCE flow (no client secret) * @param appId Application ID * @param clientId Client ID - * @param clientSecret Client secret (optional). If null or empty, PKCE flow will be used * @param redirectUri Redirect URI * @return Builder instance */ - public Builder setOAuth(String appId, String clientId, String clientSecret, String redirectUri) { + public Builder setOAuth(String appId, String clientId, String redirectUri) { // Use the builder's hostname (which defaults to Util.HOST if not set) - return setOAuth(appId, clientId, clientSecret, redirectUri, this.hostname); + return setOAuth(appId, clientId, redirectUri, this.hostname); } /** - * Configures OAuth authentication with a specific host. PKCE flow is automatically used when clientSecret is null or empty. + * Configures OAuth authentication with PKCE flow (no client secret) and specific host * @param appId Application ID * @param clientId Client ID - * @param clientSecret Client secret (optional). If null or empty, PKCE flow will be used * @param redirectUri Redirect URI * @param host API host (e.g. "api.contentstack.io", "eu-api.contentstack.com") * @return Builder instance */ - public Builder setOAuth(String appId, String clientId, String clientSecret, String redirectUri, String host) { + public Builder setOAuth(String appId, String clientId, String redirectUri, String host) { + return setOAuth(appId, clientId, redirectUri, host, null); + } + + /** + * Configures OAuth authentication with optional client secret. PKCE flow is used when clientSecret is not provided. + * @param appId Application ID + * @param clientId Client ID + * @param redirectUri Redirect URI + * @param host API host (e.g. "api.contentstack.io", "eu-api.contentstack.com") + * @param clientSecret Optional client secret. If not provided, PKCE flow will be used + * @return Builder instance + */ + public Builder setOAuth(String appId, String clientId, String redirectUri, String host, String clientSecret) { OAuthConfig.OAuthConfigBuilder builder = OAuthConfig.builder() .appId(appId) .clientId(clientId) diff --git a/src/test/java/com/contentstack/cms/oauth/OAuthTest.java b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java index 660c9176..3b09c507 100644 --- a/src/test/java/com/contentstack/cms/oauth/OAuthTest.java +++ b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java @@ -80,11 +80,11 @@ public void setup() { // Create Contentstack clients pkceClient = new Contentstack.Builder() - .setOAuth(TEST_APP_ID, TEST_CLIENT_ID, null, TEST_REDIRECT_URI) + .setOAuth(TEST_APP_ID, TEST_CLIENT_ID, TEST_REDIRECT_URI) .build(); clientSecretClient = new Contentstack.Builder() - .setOAuth(TEST_APP_ID, TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI) + .setOAuth(TEST_APP_ID, TEST_CLIENT_ID, TEST_REDIRECT_URI, Util.HOST, TEST_CLIENT_SECRET) .build(); } @@ -113,7 +113,7 @@ public void testInvalidConfigurations() { // Test invalid app ID try { new Contentstack.Builder() - .setOAuth("", TEST_CLIENT_ID, null, TEST_REDIRECT_URI) + .setOAuth("", TEST_CLIENT_ID, TEST_REDIRECT_URI) .build(); fail("Should throw exception for empty app ID"); } catch (IllegalArgumentException e) { @@ -282,7 +282,7 @@ public void testHostStorage() { // Test host storage via Contentstack.Builder Contentstack client = new Contentstack.Builder() - .setOAuth(TEST_APP_ID, TEST_CLIENT_ID, TEST_CLIENT_SECRET, TEST_REDIRECT_URI, testHost) + .setOAuth(TEST_APP_ID, TEST_CLIENT_ID, TEST_REDIRECT_URI, testHost, TEST_CLIENT_SECRET) .build(); String authUrl = client.getOAuthAuthorizationUrl(); @@ -291,7 +291,7 @@ public void testHostStorage() { // Test host storage via PKCE builder client = new Contentstack.Builder() - .setOAuth(TEST_APP_ID, TEST_CLIENT_ID, null, TEST_REDIRECT_URI, testHost) + .setOAuth(TEST_APP_ID, TEST_CLIENT_ID, TEST_REDIRECT_URI, testHost) .build(); authUrl = client.getOAuthAuthorizationUrl(); From 3a6d899a25a61d07cd608ade7004f35939380514 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 17 Sep 2025 15:07:08 +0530 Subject: [PATCH 148/197] refactor: Revise login method to support flexible TOTP/MFA authentication - Updated login method to accept a map of parameters for TOTP and MFA secret. --- .../com/contentstack/cms/Contentstack.java | 63 ++++----- .../java/com/contentstack/cms/user/User.java | 132 ++++++++++-------- .../contentstack/cms/ContentstackAPITest.java | 7 +- .../cms/ContentstackUnitTest.java | 6 +- .../contentstack/cms/user/UserUnitTests.java | 86 +++++++----- 5 files changed, 162 insertions(+), 132 deletions(-) diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 93beae95..ce03705e 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -156,62 +156,53 @@ public Response login(String emailId, String password) throws IOEx } /** - * Login with TOTP token. + * Login with two-factor authentication. This method provides flexibility to use either: + * 1. A direct 2FA token using params.put("tfaToken", "123456"), OR + * 2. An MFA secret to generate TOTP using params.put("mfaSecret", "YOUR_SECRET") + * + * Note: Do not provide both tfaToken and mfaSecret. Choose one authentication method. * * @param emailId The email ID of the user * @param password The user's password - * @param tfaToken The TOTP token + * @param params Map containing either tfaToken or mfaSecret * @return Response containing login details * @throws IOException if there's a network error + * @throws IllegalArgumentException if validation fails or if both tfaToken and mfaSecret are provided * @throws IllegalStateException if user is already logged in + * + * Example: + *
+     * // Login with direct token
+     * Map params = new HashMap<>();
+     * params.put("tfaToken", "123456");
+     * Response response = contentstack.login(email, password, params);
+     * 
+     * // OR login with MFA secret
+     * Map params = new HashMap<>();
+     * params.put("mfaSecret", "YOUR_SECRET");
+     * Response response = contentstack.login(email, password, params);
+     * 
*/ - public Response login(String emailId, String password, String tfaToken) throws IOException { - return login(emailId, password, tfaToken, null); - } - - /** - * Login with TOTP/MFA support. - * - * @param emailId The email ID of the user - * @param password The user's password - * @param tfaToken The TOTP token (can be null if mfaSecret is provided) - * @param mfaSecret The MFA secret key to generate TOTP (optional if tfaToken is provided) - * @return Response containing login details - * @throws IOException if there's a network error - * @throws IllegalArgumentException if both tfaToken and mfaSecret are null or if secret is invalid - * @throws IllegalStateException if user is already logged in - */ - public Response login(String emailId, String password, String tfaToken, String mfaSecret) throws IOException { + public Response login(String emailId, String password, Map params) throws IOException { // Check if already logged in if (this.authtoken != null) { throw new IllegalStateException(Util.USER_ALREADY_LOGGED_IN); } - // Validate inputs - if (emailId == null || emailId.trim().isEmpty()) { + // Validate basic inputs + if (emailId.trim().isEmpty()) { throw new IllegalArgumentException("Email is required"); } - if (password == null || password.trim().isEmpty()) { + if (password.trim().isEmpty()) { throw new IllegalArgumentException("Password is required"); } - if ((tfaToken == null || tfaToken.trim().isEmpty()) && (mfaSecret == null || mfaSecret.trim().isEmpty())) { - throw new IllegalArgumentException("Either tfaToken or mfaSecret must be provided"); - } - - // Generate TOTP if needed - String finalTfaToken = tfaToken; - if ((tfaToken == null || tfaToken.trim().isEmpty()) && mfaSecret != null && !mfaSecret.trim().isEmpty()) { - try { - GoogleAuthenticator gAuth = new GoogleAuthenticator(); - finalTfaToken = String.format("%06d", gAuth.getTotpPassword(mfaSecret)); - } catch (Exception e) { - throw new IllegalArgumentException("Failed to generate TOTP token: " + e.getMessage(), e); - } + if (params.isEmpty()) { + throw new IllegalArgumentException("Authentication parameters are required"); } // Perform login user = new User(this.instance); - Response response = user.login(emailId, password, finalTfaToken).execute(); + Response response = user.login(emailId, password, params).execute(); setupLoginCredentials(response); return response; } diff --git a/src/main/java/com/contentstack/cms/user/User.java b/src/main/java/com/contentstack/cms/user/User.java index d8839468..31926221 100644 --- a/src/main/java/com/contentstack/cms/user/User.java +++ b/src/main/java/com/contentstack/cms/user/User.java @@ -11,14 +11,11 @@ import com.warrenstrange.googleauth.GoogleAuthenticator; import java.util.HashMap; +import java.util.Map; /** - * All accounts registered with Contentstack are known as Users. - * A Stack - * can have many users with varying - * permissions and roles. + * All accounts registered with Contentstack are known as Users. + * A stack can have many users with varying permissions and roles. * * @author ***REMOVED*** * @version v0.1.0 @@ -27,8 +24,8 @@ public class User implements BaseImplementation { protected final UserService userService; - protected HashMap headers; - protected HashMap params; + protected final HashMap headers; + protected final HashMap params; /** * @param client Retrofit adapts a Java interface to HTTP calls by using @@ -67,8 +64,13 @@ public User(Retrofit client) { * @return Call */ public Call login(@NotNull String email, @NotNull String password) { + HashMap credentials = new HashMap<>(); + credentials.put("email", email); + credentials.put("password", password); + HashMap> userSession = new HashMap<>(); - userSession.put("user", setCredentials(email, password)); + userSession.put("user", credentials); + JSONObject userDetail = new JSONObject(userSession); return this.userService.login(loginHeader(), userDetail); } @@ -80,85 +82,95 @@ private HashMap loginHeader() { } /** - * Login with TOTP token for two-factor authentication. + * Login with two-factor authentication. This method provides flexibility to use either: + * 1. A direct 2FA token using params.put("tfaToken", "123456"), OR + * 2. An MFA secret to generate TOTP using params.put("mfaSecret", "YOUR_SECRET") + * + * Note: Do not provide both tfaToken and mfaSecret. Choose one authentication method. * - * @param email email for user to login - * @param password password for user to login - * @param tfaToken the TOTP token for two-factor authentication + * @param email email for user to login + * @param password password for user to login + * @param params Map containing either tfaToken or mfaSecret * @return Call containing login details - * @throws IllegalArgumentException if email, password, or tfaToken is null or empty + * @throws IllegalArgumentException if validation fails or if both tfaToken and mfaSecret are provided + * + * Example: + *
+     * // Login with direct token
+     * Map params = new HashMap<>();
+     * params.put("tfaToken", "123456");
+     * Call call = user.login(email, password, params);
+     * 
+     * // OR login with MFA secret
+     * Map params = new HashMap<>();
+     * params.put("mfaSecret", "YOUR_SECRET");
+     * Call call = user.login(email, password, params);
+     * 
*/ - public Call login(@NotNull String email, @NotNull String password, String tfaToken) { - if (tfaToken == null || tfaToken.trim().isEmpty()) { - throw new IllegalArgumentException("TOTP token cannot be null or empty"); - } - HashMap> userSession = new HashMap<>(); - userSession.put("user", setCredentials(email, password, tfaToken)); - JSONObject userDetail = new JSONObject(userSession); - return this.userService.login(loginHeader(), userDetail); - } - - private HashMap setCredentials(@NotNull String... arguments) { - if (arguments == null || arguments.length < 2) { - throw new IllegalArgumentException("Email and password are required"); + public Call login(@NotNull String email, @NotNull String password, @NotNull Map params) { + // Validate basic inputs + if (email.trim().isEmpty()) { + throw new IllegalArgumentException("Email is required"); } - if (arguments[0] == null || arguments[0].trim().isEmpty()) { - throw new IllegalArgumentException("Email cannot be null or empty"); + if (password.trim().isEmpty()) { + throw new IllegalArgumentException("Password is required"); } - if (arguments[1] == null || arguments[1].trim().isEmpty()) { - throw new IllegalArgumentException("Password cannot be null or empty"); + if (params.isEmpty()) { + throw new IllegalArgumentException("Authentication parameters are required"); } + + String tfaToken = params.get("tfaToken"); + String mfaSecret = params.get("mfaSecret"); - HashMap credentials = new HashMap<>(); - credentials.put("email", arguments[0]); - credentials.put("password", arguments[1]); + // Check for mutual exclusivity + boolean hasTfaToken = tfaToken != null && !tfaToken.trim().isEmpty(); + boolean hasMfaSecret = mfaSecret != null && !mfaSecret.trim().isEmpty(); - if (arguments.length > 2 && arguments[2] != null && !arguments[2].trim().isEmpty()) { - credentials.put("tfa_token", arguments[2]); + if (hasTfaToken && hasMfaSecret) { + throw new IllegalArgumentException("Cannot provide both tfaToken and mfaSecret. Use either one."); } - return credentials; - } - - public Call login(@NotNull String email, @NotNull String password, String tfaToken, String mfaSecret) { - if (email.trim().isEmpty()) { - throw new IllegalArgumentException("Email cannot be empty"); - } - if (password.trim().isEmpty()) { - throw new IllegalArgumentException("Password cannot be empty"); - } - if ((tfaToken == null || tfaToken.trim().isEmpty()) && (mfaSecret == null || mfaSecret.trim().isEmpty())) { - throw new IllegalArgumentException("Either tfaToken or mfaSecret must be provided"); + if (!hasTfaToken && !hasMfaSecret) { + throw new IllegalArgumentException("Must provide either tfaToken or mfaSecret"); } + // Generate TOTP if needed String finalTfaToken = tfaToken; - if ((tfaToken == null || tfaToken.trim().isEmpty()) && mfaSecret != null && !mfaSecret.trim().isEmpty()) { - try { - finalTfaToken = generateTOTP(mfaSecret); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Failed to generate TOTP token: " + e.getMessage(), e); - } - } - if (finalTfaToken == null || finalTfaToken.trim().isEmpty()) { - throw new IllegalArgumentException("Failed to generate valid TOTP token"); + if (!hasTfaToken && hasMfaSecret) { + finalTfaToken = generateTOTP(mfaSecret); } - return login(email, password, finalTfaToken); + + // Perform login + HashMap credentials = new HashMap<>(); + credentials.put("email", email); + credentials.put("password", password); + credentials.put("tfa_token", finalTfaToken); + + HashMap> userSession = new HashMap<>(); + userSession.put("user", credentials); + + JSONObject userDetail = new JSONObject(userSession); + return this.userService.login(loginHeader(), userDetail); } private String generateTOTP(String secret) { if (secret == null || secret.trim().isEmpty()) { throw new IllegalArgumentException("MFA secret cannot be null or empty"); } + try { GoogleAuthenticator gAuth = new GoogleAuthenticator(); String totp = String.format("%06d", gAuth.getTotpPassword(secret)); + + // Validate the generated token if (!totp.matches("\\d{6}")) { - throw new IllegalArgumentException("Generated TOTP token is invalid"); + throw new IllegalArgumentException("Generated TOTP token is invalid (not 6 digits)"); } + return totp; } catch (IllegalArgumentException e) { throw e; } catch (Exception e) { - throw new IllegalArgumentException("Invalid MFA secret key: " + e.getMessage(), e); + throw new IllegalArgumentException("Failed to generate TOTP token: " + e.getMessage(), e); } } diff --git a/src/test/java/com/contentstack/cms/ContentstackAPITest.java b/src/test/java/com/contentstack/cms/ContentstackAPITest.java index 6f1ca163..61ffba90 100644 --- a/src/test/java/com/contentstack/cms/ContentstackAPITest.java +++ b/src/test/java/com/contentstack/cms/ContentstackAPITest.java @@ -9,6 +9,8 @@ import retrofit2.Response; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; /* @author ***REMOVED***@gmail.com @@ -69,8 +71,9 @@ void testContentstackUserLoginWhenAlreadyLoggedIn() throws IOException { Contentstack contentstack = new Contentstack.Builder() .setAuthtoken(null) .build(); - Response response = contentstack.login("invalid@credentials.com", "invalid@password", - "invalid_tfa_token"); + Map params = new HashMap<>(); + params.put("tfaToken", "invalid_tfa_token"); + Response response = contentstack.login("invalid@credentials.com", "invalid@password", params); Assertions.assertEquals(422, response.code()); } diff --git a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java index 6386cf22..8c9e57dc 100644 --- a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java +++ b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java @@ -17,6 +17,8 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; +import java.util.HashMap; +import java.util.Map; public class ContentstackUnitTest { @@ -210,7 +212,9 @@ void testSetAuthtokenLogin() { void testSetAuthtokenLoginWithTfa() { Contentstack client = new Contentstack.Builder().setAuthtoken("fake@authtoken").build(); try { - client.login("fake@email.com", "fake@password", "fake@tfa"); + Map params = new HashMap<>(); + params.put("tfaToken", "fake@tfa"); + client.login("fake@email.com", "fake@password", params); } catch (Exception e) { Assertions.assertEquals("User is already loggedIn, Please logout then try to login again", e.getMessage()); } diff --git a/src/test/java/com/contentstack/cms/user/UserUnitTests.java b/src/test/java/com/contentstack/cms/user/UserUnitTests.java index 3d5484d2..150eea96 100644 --- a/src/test/java/com/contentstack/cms/user/UserUnitTests.java +++ b/src/test/java/com/contentstack/cms/user/UserUnitTests.java @@ -10,6 +10,7 @@ import java.net.URL; import java.util.HashMap; +import java.util.Map; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -587,8 +588,7 @@ void testUserBaseParameters() { HashMap headers = new HashMap<>(); headers.put("header", "something"); Request requestInfo = userInstance. - addParams(headers). - addParam("param", "value") + addParam("param", (Object)"value") .addHeader("key", "value") .addHeaders(headers) .logoutWithAuthtoken("authtoken") @@ -602,14 +602,16 @@ void testUserBaseParameters() { @DisplayName("Test login with TOTP token") void testLoginWithTOTPToken() { // Test basic login with TOTP token - Request requestInfo = userInstance.login("test@example.com", "password", "123456").request(); + Map params = new HashMap<>(); + params.put("tfaToken", "123456"); + Request requestInfo = userInstance.login("test@example.com", "password", params).request(); // Verify request format Assertions.assertEquals("POST", requestInfo.method()); Assertions.assertEquals("/v3/user-session", requestInfo.url().encodedPath()); // Verify request body - String requestBody = requestInfo.body().toString(); + String requestBody = requestInfo.body() != null ? requestInfo.body().toString() : ""; Assertions.assertTrue(requestBody.contains("\"tfa_token\":\"123456\"")); } @@ -617,31 +619,35 @@ void testLoginWithTOTPToken() { @Order(43) @DisplayName("Test login with MFA parameters") void testLoginWithMFAParameters() { - // Test login with both token and secret (token should be used) - Request requestInfo = userInstance.login("test@example.com", "password", "123456", "test-secret").request(); + // Test login with both token and secret (should throw exception) + Map params = new HashMap<>(); + params.put("tfaToken", "123456"); + params.put("mfaSecret", "test-secret"); - // Verify request format - Assertions.assertEquals("POST", requestInfo.method()); - Assertions.assertEquals("/v3/user-session", requestInfo.url().encodedPath()); + IllegalArgumentException exception = Assertions.assertThrows( + IllegalArgumentException.class, + () -> userInstance.login("test@example.com", "password", params).request() + ); - // Verify request body uses token - String requestBody = requestInfo.body().toString(); - Assertions.assertTrue(requestBody.contains("\"tfa_token\":\"123456\"")); + Assertions.assertTrue(exception.getMessage().contains("Cannot provide both tfaToken and mfaSecret")); } @Test @Order(44) @DisplayName("Test login validation") void testLoginValidation() { + Map params = new HashMap<>(); + params.put("tfaToken", "123456"); + // Test with missing required parameters Assertions.assertThrows(IllegalArgumentException.class, - () -> userInstance.login(null, "password", "123456").request()); + () -> userInstance.login("", "password", params).request()); Assertions.assertThrows(IllegalArgumentException.class, - () -> userInstance.login("test@example.com", null, "123456").request()); + () -> userInstance.login("test@example.com", "", params).request()); Assertions.assertThrows(IllegalArgumentException.class, - () -> userInstance.login("test@example.com", "password", null).request()); + () -> userInstance.login("test@example.com", "password", new HashMap<>()).request()); } @Test @@ -649,14 +655,16 @@ void testLoginValidation() { @DisplayName("Test TOTP generation from MFA secret") void testTOTPGenerationFromSecret() { // Test login with MFA secret only - Request requestInfo = userInstance.login("test@example.com", "password", null, "test-secret").request(); + Map params = new HashMap<>(); + params.put("mfaSecret", "test-secret"); + Request requestInfo = userInstance.login("test@example.com", "password", params).request(); // Verify request format Assertions.assertEquals("POST", requestInfo.method()); Assertions.assertEquals("/v3/user-session", requestInfo.url().encodedPath()); // Verify generated TOTP format - String requestBody = requestInfo.body().toString(); + String requestBody = requestInfo.body() != null ? requestInfo.body().toString() : ""; Assertions.assertTrue(requestBody.contains("\"tfa_token\":\"")); // Extract TOTP token and verify it's 6 digits @@ -669,9 +677,12 @@ void testTOTPGenerationFromSecret() { @DisplayName("Test invalid MFA secret handling") void testInvalidMFASecret() { // Test with invalid MFA secret + Map params = new HashMap<>(); + params.put("mfaSecret", "invalid-secret"); + IllegalArgumentException exception = Assertions.assertThrows( IllegalArgumentException.class, - () -> userInstance.login("test@example.com", "password", null, "invalid-secret").request() + () -> userInstance.login("test@example.com", "password", params).request() ); Assertions.assertTrue(exception.getMessage().contains("Invalid MFA secret key")); @@ -679,32 +690,41 @@ void testInvalidMFASecret() { @Test @Order(47) - @DisplayName("Test TOTP token priority over MFA secret") - void testTOTPPriority() { - // When both token and secret are provided, token should be used - Request requestInfo = userInstance.login("test@example.com", "password", "123456", "test-secret").request(); - String requestBody = requestInfo.body().toString(); + @DisplayName("Test empty parameters") + void testEmptyParameters() { + // Test with empty parameters map + Map params = new HashMap<>(); - // Should use provided token, not generate from secret - Assertions.assertTrue(requestBody.contains("\"tfa_token\":\"123456\"")); + IllegalArgumentException exception = Assertions.assertThrows( + IllegalArgumentException.class, + () -> userInstance.login("test@example.com", "password", params).request() + ); + + Assertions.assertTrue(exception.getMessage().contains("Must provide either tfaToken or mfaSecret")); } @Test @Order(48) - @DisplayName("Test null/empty MFA parameters") - void testNullEmptyMFAParams() { - // Test with both null + @DisplayName("Test empty values in parameters") + void testEmptyValues() { + // Test with empty tfaToken + Map params1 = new HashMap<>(); + params1.put("tfaToken", ""); + IllegalArgumentException exception1 = Assertions.assertThrows( IllegalArgumentException.class, - () -> userInstance.login("test@example.com", "password", null, null).request() + () -> userInstance.login("test@example.com", "password", params1).request() ); - Assertions.assertTrue(exception1.getMessage().contains("Either tfaToken or mfaSecret must be provided")); + Assertions.assertTrue(exception1.getMessage().contains("Must provide either tfaToken or mfaSecret")); + + // Test with empty mfaSecret + Map params2 = new HashMap<>(); + params2.put("mfaSecret", ""); - // Test with empty secret IllegalArgumentException exception2 = Assertions.assertThrows( IllegalArgumentException.class, - () -> userInstance.login("test@example.com", "password", null, "").request() + () -> userInstance.login("test@example.com", "password", params2).request() ); - Assertions.assertTrue(exception2.getMessage().contains("Either tfaToken or mfaSecret must be provided")); + Assertions.assertTrue(exception2.getMessage().contains("Must provide either tfaToken or mfaSecret")); } } From a87c5f824481869343104018ccf63d5da067ded4 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 17 Sep 2025 16:00:15 +0530 Subject: [PATCH 149/197] chore: formatting --- .../java/com/contentstack/cms/Contentstack.java | 15 +++++++-------- src/main/java/com/contentstack/cms/user/User.java | 12 ++++++------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 4673b4fc..1f01dbcb 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -173,14 +173,14 @@ public Response login(String emailId, String password) throws IOEx * Example: *
      * // Login with direct token
-     * Map params = new HashMap<>();
-     * params.put("tfaToken", "123456");
-     * Response response = contentstack.login(email, password, params);
+     * {@code Map params = new HashMap<>();}
+     * {@code params.put("tfaToken", "123456");}
+     * {@code Response response = contentstack.login(email, password, params);}
      * 
      * // OR login with MFA secret
-     * Map params = new HashMap<>();
-     * params.put("mfaSecret", "YOUR_SECRET");
-     * Response response = contentstack.login(email, password, params);
+     * {@code Map params = new HashMap<>();}
+     * {@code params.put("mfaSecret", "YOUR_SECRET");}
+     * {@code Response response = contentstack.login(email, password, params);}
      * 
*/ public Response login(String emailId, String password, Map params) throws IOException { @@ -691,8 +691,7 @@ public Builder setTimeout(int timeout) { * single-user application. The tuning parameters in this pool are * subject to change in future OkHttp releases. Currently, this pool * holds up to 5 idle connections which will be evicted after 5 minutes - * of inactivity. - *

+ * of inactivity *

* public ConnectionPool() { this(5, 5, TimeUnit.MINUTES); } * diff --git a/src/main/java/com/contentstack/cms/user/User.java b/src/main/java/com/contentstack/cms/user/User.java index 31926221..fb549976 100644 --- a/src/main/java/com/contentstack/cms/user/User.java +++ b/src/main/java/com/contentstack/cms/user/User.java @@ -97,14 +97,14 @@ private HashMap loginHeader() { * Example: *

      * // Login with direct token
-     * Map params = new HashMap<>();
-     * params.put("tfaToken", "123456");
-     * Call call = user.login(email, password, params);
+     * {@code Map params = new HashMap<>();}
+     * {@code params.put("tfaToken", "123456");}
+     * {@code Call call = user.login(email, password, params);}
      * 
      * // OR login with MFA secret
-     * Map params = new HashMap<>();
-     * params.put("mfaSecret", "YOUR_SECRET");
-     * Call call = user.login(email, password, params);
+     * {@code Map params = new HashMap<>();}
+     * {@code params.put("mfaSecret", "YOUR_SECRET");}
+     * {@code Call call = user.login(email, password, params);}
      * 
*/ public Call login(@NotNull String email, @NotNull String password, @NotNull Map params) { From 03e935d87a52ef5be9f7d304dda334ce286362a6 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 23 Sep 2025 11:41:11 +0530 Subject: [PATCH 150/197] chore: version bump --- changelog.md | 8 +++++++- pom.xml | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 222ab339..3cffab6a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,8 +1,14 @@ # Changelog +## v1.9.0 + +### Oct 06, 2025 + +- Enhancement : TOTP login added + ## v1.8.0 -### Sep 15, 2025 +### Sep 29, 2025 - Feature : OAuth 2.0 support with PKCE flow - Improved code organization and removed redundant methods diff --git a/pom.xml b/pom.xml index 4b788008..4a401af9 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.8.0 + 1.9.0 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach From 861c5c6e2710c2123b96a0748ac095eca62a2126 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 29 Sep 2025 09:15:03 +0530 Subject: [PATCH 151/197] feat: variants support added --- .../com/contentstack/cms/stack/Stack.java | 23 ++ .../contentstack/cms/stack/VariantGroup.java | 236 ++++++++++++++++++ .../cms/stack/VariantsService.java | 32 +++ .../java/com/contentstack/cms/TestClient.java | 2 + 4 files changed, 293 insertions(+) create mode 100644 src/main/java/com/contentstack/cms/stack/VariantGroup.java create mode 100644 src/main/java/com/contentstack/cms/stack/VariantsService.java diff --git a/src/main/java/com/contentstack/cms/stack/Stack.java b/src/main/java/com/contentstack/cms/stack/Stack.java index 35af7ccd..19dbe043 100644 --- a/src/main/java/com/contentstack/cms/stack/Stack.java +++ b/src/main/java/com/contentstack/cms/stack/Stack.java @@ -377,6 +377,29 @@ public GlobalField globalField(@NotNull String globalFiledUid) { return new GlobalField(this.client,this.headers,globalFiledUid); } + /** + * Creates a new instance of VariantGroup for managing variant groups. + * This method is used when you want to create a new variant group. + * + * @return A new VariantGroup instanceroup variantGroup() { + return new VariantGroup(this.client,this.headers); + } + */ + public VariantGroup variantGroup() { + return new VariantGroup(this.client,this.headers); + } + + /** + * Creates a new instance of VariantGroup for managing a specific variant group. + * This method is used when you want to work with an existing variant group. + * + * @param variantGroupUid The UID of the variant group to manage + * @return A new VariantGroup instance configured for the specified variant group + */ + public VariantGroup variantGroup(@NotNull String variantGroupUid) { + return new VariantGroup(this.client,this.headers,variantGroupUid); + } + /** * Contentstack has a sophisticated multilingual capability. It allows you to * create and publish entries in any diff --git a/src/main/java/com/contentstack/cms/stack/VariantGroup.java b/src/main/java/com/contentstack/cms/stack/VariantGroup.java new file mode 100644 index 00000000..e05d8c81 --- /dev/null +++ b/src/main/java/com/contentstack/cms/stack/VariantGroup.java @@ -0,0 +1,236 @@ +package com.contentstack.cms.stack; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.NotNull; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import com.contentstack.cms.BaseImplementation; + +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.Retrofit; + +/** + * The VariantGroup class provides functionality to manage variant groups in Contentstack. + * Variant groups allow you to manage different versions of your content for various use cases, + * such as A/B testing, localization, or personalization. + */ +public class VariantGroup implements BaseImplementation { + protected final VariantsService service; + protected final Map headers; + protected Map params; + private final Retrofit instance; + private String variantGroupUid; + private List branches; + + /** + * Creates a new VariantGroup instance without a specific variant group UID. + * This constructor is used when creating new variant groups. + * + * @param instance The Retrofit instance for making API calls + * @param headers The headers to be included in API requests + */ + protected VariantGroup(Retrofit instance, Map headers) { + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.params = new HashMap<>(); + this.instance = instance; + this.service = instance.create(VariantsService.class); + this.branches = Arrays.asList("main"); // Default to main branch + } + + /** + * Creates a new VariantGroup instance with a specific variant group UID. + * This constructor is used when working with existing variant groups. + * + * @param instance The Retrofit instance for making API calls + * @param headers The headers to be included in API requests + * @param variantGroupUid The unique identifier of the variant group + */ + protected VariantGroup(Retrofit instance, Map headers, String variantGroupUid) { + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.params = new HashMap<>(); + this.instance = instance; + this.variantGroupUid = variantGroupUid; + this.service = instance.create(VariantsService.class); + this.branches = Arrays.asList("main"); // Default to main branch + } + + /** + * Validates that the variant group UID is not null or empty. + * This method is called before operations that require a valid variant group UID. + * + * @throws IllegalAccessError if the variant group UID is null or empty + */ + void validate() { + if (this.variantGroupUid == null || this.variantGroupUid.isEmpty()) + throw new IllegalAccessError("Variant group uid can not be null or empty"); + } + + /** + * Sets the branches for the variant group using a List of branch names. + * These branches will be used when linking or unlinking content types to the variant group. + * + * @param branches A List of String values representing the branch names + * @return The current VariantGroup instance for method chaining + */ + public VariantGroup setBranches(List branches) { + this.branches = branches; + return this; + } + + /** + * Sets the branches for the variant group using varargs (variable number of arguments). + * This is a convenience method that allows passing branch names directly as arguments. + * These branches will be used when linking or unlinking content types to the variant group. + * + * @param branches Variable number of String arguments representing branch names + * @return The current VariantGroup instance for method chaining + */ + public VariantGroup setBranches(String... branches) { + this.branches = Arrays.asList(branches); + return this; + } + + /** + * @param key A string representing the key of the parameter. It cannot be + * null and must be + * provided as a non-null value. + * @param value The "value" parameter is of type Object, which means it can + * accept any type of + * object as its value. + * @return instance of Asset + */ + @Override + public VariantGroup addParam(@NotNull String key, @NotNull Object value) { + this.params.put(key, value); + return this; + } + + /** + * @param key The key parameter is a string that represents the name or + * identifier of the header. + * It is used to specify the type of information being sent in the + * header. + * @param value The value parameter is a string that represents the value of the + * header. + * @return instance of Asset + */ + @Override + public VariantGroup addHeader(@NotNull String key, @NotNull String value) { + this.headers.put(key, value); + return this; + } + + /** + * @param headers A HashMap containing key-value pairs of headers, where the key + * is a String + * representing the header name and the value is a String + * representing the header value. + * @return instance of Asset + */ + @Override + public VariantGroup addHeaders(@NotNull HashMap headers) { + this.headers.putAll(headers); + return this; + } + + + /** + * @param headers The "params" parameter is a HashMap that maps String keys to + * Object values. It is + * annotated with @NotNull, indicating that it cannot be null. + * @return instance of Asset + */ + @Override + public VariantGroup addParams(@NotNull HashMap headers) { + this.params.putAll(headers); + return this; + } + + + /** + * clears all params in the request + */ + protected void clearParams() { + this.params.clear(); + } + + /** + * Retrieves a list of all variant groups. + * This method does not require a variant group UID to be set. + * + * @return A Call object that can be executed to perform the API request to fetch all variant groups + */ + public Call find() { + return this.service.fetchVariantGroups(this.headers, this.params); + } + + /** + * Links content types to the variant group. + * + * @param contentTypeUids Array of content type UIDs to link to the variant group + * @return A Call object that can be executed to perform the API request + * @throws IllegalAccessError if the variant group UID is not set + * @throws IllegalArgumentException if contentTypeUids is empty + */ + public Call linkContentTypes(@NotNull String... contentTypeUids) { + if (contentTypeUids.length == 0) { + throw new IllegalArgumentException("Content type UIDs cannot be empty"); + } + return updateContentTypeLinks(contentTypeUids, true); + } + + /** + * Unlinks content types from the variant group. + * + * @param contentTypeUids Array of content type UIDs to unlink from the variant group + * @return A Call object that can be executed to perform the API request + * @throws IllegalAccessError if the variant group UID is not set + * @throws IllegalArgumentException if contentTypeUids is empty + */ + public Call unlinkContentTypes(@NotNull String... contentTypeUids) { + if (contentTypeUids.length == 0) { + throw new IllegalArgumentException("Content type UIDs cannot be empty"); + } + return updateContentTypeLinks(contentTypeUids, false); + } + + /** + * Updates the linking status of content types to a variant group. + * This private method handles both linking and unlinking operations. + * + * @param contentTypeUids Array of content type UIDs to update + * @param isLink true to link content types, false to unlink + * @return A Call object that can be executed to perform the API request + * @throws IllegalAccessError if the variant group UID is not set + */ + @SuppressWarnings("unchecked") + private Call updateContentTypeLinks(String[] contentTypeUids, boolean isLink) { + validate(); + + // Construct the request body + JSONObject requestBody = new JSONObject(); + JSONArray contentTypes = new JSONArray(); + JSONArray branches = new JSONArray(); + for (String branch : this.branches) { + branches.add(branch); + } + + for (String uid : contentTypeUids) { + JSONObject contentType = new JSONObject(); + contentType.put("uid", uid); + contentType.put("status", isLink ? "linked" : "unlinked"); + contentTypes.add(contentType); + } + requestBody.put("uid", this.variantGroupUid); + requestBody.put("branches", branches); + requestBody.put("content_types", contentTypes); + return this.service.updateVariantGroupContentTypes(this.headers, this.variantGroupUid, this.params, requestBody); + } +} \ No newline at end of file diff --git a/src/main/java/com/contentstack/cms/stack/VariantsService.java b/src/main/java/com/contentstack/cms/stack/VariantsService.java new file mode 100644 index 00000000..43bdba5c --- /dev/null +++ b/src/main/java/com/contentstack/cms/stack/VariantsService.java @@ -0,0 +1,32 @@ +package com.contentstack.cms.stack; + +import java.util.Map; + +import org.json.simple.JSONObject; + +import okhttp3.ResponseBody; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.HeaderMap; +import retrofit2.http.PUT; +import retrofit2.http.Path; +import retrofit2.http.QueryMap; + +/** + * Service interface for variant group related API endpoints. + */ +public interface VariantsService { + + @GET("variant_groups") + Call fetchVariantGroups( + @HeaderMap Map headers, + @QueryMap Map queryParam); + + @PUT("variant_groups/{variant_group_uid}/variants") + Call updateVariantGroupContentTypes( + @HeaderMap Map headers, + @Path("variant_group_uid") String variantGroupUid, + @QueryMap Map queryParam, + @Body JSONObject body); +} \ No newline at end of file diff --git a/src/test/java/com/contentstack/cms/TestClient.java b/src/test/java/com/contentstack/cms/TestClient.java index 37444610..f92d7781 100644 --- a/src/test/java/com/contentstack/cms/TestClient.java +++ b/src/test/java/com/contentstack/cms/TestClient.java @@ -23,6 +23,8 @@ public class TestClient { public final static String DEV_HOST = "api.contentstack.io"; // (env.get("dev_host") != null) ? env.get("dev_host") : "api.contentstack.io"; + public final static String VARIANT_GROUP_UID = (env.get("variantGroupUid") != null) ? env.get("variantGroupUid") + : "variantGroupUid99999999"; private static Contentstack instance; private static Stack stackInstance; From a658c29ae15024f1c17053fdae20857cbaf73ed3 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 29 Sep 2025 09:17:45 +0530 Subject: [PATCH 152/197] chore: testcases added for variants support --- .../java/com/contentstack/cms/TestClient.java | 1 + .../cms/models/LoginDetailTest.java | 1 + .../cms/stack/APISanityTestSuite.java | 6 +- .../cms/stack/VariantGroupAPITest.java | 43 +++++ .../cms/stack/VariantGroupTest.java | 170 ++++++++++++++++++ 5 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/contentstack/cms/stack/VariantGroupAPITest.java create mode 100644 src/test/java/com/contentstack/cms/stack/VariantGroupTest.java diff --git a/src/test/java/com/contentstack/cms/TestClient.java b/src/test/java/com/contentstack/cms/TestClient.java index f92d7781..ec5484a3 100644 --- a/src/test/java/com/contentstack/cms/TestClient.java +++ b/src/test/java/com/contentstack/cms/TestClient.java @@ -17,6 +17,7 @@ public class TestClient { public final static String USER_ID = (env.get("userId") != null) ? env.get("userId") : "c11e668e0295477f"; public final static String OWNERSHIP = (env.get("ownershipToken") != null) ? env.get("ownershipToken") : "ownershipTokenId"; + // file deepcode ignore NonCryptoHardcodedSecret/test: public final static String API_KEY = (env.get("apiKey") != null) ? env.get("apiKey") : "apiKey99999999"; public final static String MANAGEMENT_TOKEN = (env.get("managementToken") != null) ? env.get("managementToken") : "managementToken99999999"; diff --git a/src/test/java/com/contentstack/cms/models/LoginDetailTest.java b/src/test/java/com/contentstack/cms/models/LoginDetailTest.java index b73ec23f..5e070556 100644 --- a/src/test/java/com/contentstack/cms/models/LoginDetailTest.java +++ b/src/test/java/com/contentstack/cms/models/LoginDetailTest.java @@ -102,6 +102,7 @@ void getterSetterUserModelLastName() { @Test void getterSetterUserModelUsername() { UserModel userModel = new UserModel(); + // deepcode ignore NoHardcodedCredentials/test: userModel.setUsername("***REMOVED***"); Assertions.assertEquals("***REMOVED***", userModel.getUsername()); diff --git a/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java b/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java index afd01049..8aa24dce 100644 --- a/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java +++ b/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java @@ -19,9 +19,11 @@ StackAPITest.class, TokenAPITest.class, OrgApiTests.class, - GlobalFieldAPITest.class + GlobalFieldAPITest.class, + VariantGroupAPITest.class, + VariantGroupTest.class - }) +}) public class APISanityTestSuite { } \ No newline at end of file diff --git a/src/test/java/com/contentstack/cms/stack/VariantGroupAPITest.java b/src/test/java/com/contentstack/cms/stack/VariantGroupAPITest.java new file mode 100644 index 00000000..7bd7c062 --- /dev/null +++ b/src/test/java/com/contentstack/cms/stack/VariantGroupAPITest.java @@ -0,0 +1,43 @@ +package com.contentstack.cms.stack; + +import java.io.IOException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.contentstack.cms.TestClient; + +import okhttp3.Request; + +class VariantGroupAPITest { + + private static final String API_KEY = TestClient.API_KEY; + private static final String MANAGEMENT_TOKEN = TestClient.MANAGEMENT_TOKEN; + private static final String VARIANT_GROUP_UID = TestClient.VARIANT_GROUP_UID; + private final VariantGroup variantGroup = TestClient.getClient().stack(API_KEY, MANAGEMENT_TOKEN).variantGroup(); + + @Test + void testFetchVariantGroups() throws IOException, InterruptedException { + variantGroup.addParam("include_count", true); + variantGroup.addParam("include_variant_info", true); + Request request = variantGroup.find().request(); + Assertions.assertEquals("GET", request.method()); + Assertions.assertEquals("https://api.contentstack.io/v3/variant_groups?include_variant_info=true&include_count=true", request.url().toString()); + } + + @Test + void testLinkContentTypes() throws IOException, InterruptedException { + VariantGroup variantGroupWithUID = TestClient.getClient().stack(API_KEY, MANAGEMENT_TOKEN).variantGroup(VARIANT_GROUP_UID); + Request request = variantGroupWithUID.linkContentTypes("author", "page").request(); + Assertions.assertEquals("PUT", request.method()); + Assertions.assertEquals("https://api.contentstack.io/v3/variant_groups/" + VARIANT_GROUP_UID + "/variants", request.url().toString()); + } + + @Test + void testUnlinkContentTypes() throws IOException, InterruptedException { + VariantGroup variantGroupWithUID = TestClient.getClient().stack(API_KEY, MANAGEMENT_TOKEN).variantGroup(VARIANT_GROUP_UID); + Request request = variantGroupWithUID.unlinkContentTypes("author", "page").request(); + Assertions.assertEquals("PUT", request.method()); + Assertions.assertEquals("https://api.contentstack.io/v3/variant_groups/" + VARIANT_GROUP_UID + "/variants", request.url().toString()); + } +} \ No newline at end of file diff --git a/src/test/java/com/contentstack/cms/stack/VariantGroupTest.java b/src/test/java/com/contentstack/cms/stack/VariantGroupTest.java new file mode 100644 index 00000000..5bb5ca03 --- /dev/null +++ b/src/test/java/com/contentstack/cms/stack/VariantGroupTest.java @@ -0,0 +1,170 @@ +package com.contentstack.cms.stack; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.contentstack.cms.TestClient; + +import okhttp3.Request; +import okio.Buffer; + +class VariantGroupTest { + + private static final String API_KEY = TestClient.API_KEY; + private static final String MANAGEMENT_TOKEN = TestClient.MANAGEMENT_TOKEN; + private static final String VARIANT_GROUP_UID = TestClient.VARIANT_GROUP_UID; + private VariantGroup variantGroup; + + @BeforeEach + void setUp() { + variantGroup = TestClient.getClient().stack(API_KEY, MANAGEMENT_TOKEN).variantGroup(VARIANT_GROUP_UID); + } + + @Test + void testValidate_WithValidUID() { + Assertions.assertDoesNotThrow(() -> variantGroup.validate()); + } + + @Test + void testValidate_WithNullUID() { + VariantGroup group = TestClient.getClient().stack(API_KEY, MANAGEMENT_TOKEN).variantGroup(); + IllegalAccessError exception = Assertions.assertThrows(IllegalAccessError.class, group::validate); + Assertions.assertNotNull(exception.getMessage()); + } + + @Test + void testValidate_WithEmptyUID() { + VariantGroup group = TestClient.getClient().stack(API_KEY, MANAGEMENT_TOKEN).variantGroup(""); + IllegalAccessError exception = Assertions.assertThrows(IllegalAccessError.class, group::validate); + Assertions.assertNotNull(exception.getMessage()); + } + + @Test + void testLinkContentTypes_WithEmptyArray() { + IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, + () -> variantGroup.linkContentTypes()); + Assertions.assertNotNull(exception.getMessage()); + } + + @Test + void testUnlinkContentTypes_WithEmptyArray() { + IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, + () -> variantGroup.unlinkContentTypes()); + Assertions.assertNotNull(exception.getMessage()); + } + + @Test + void testLinkContentTypes_SingleContentType() throws IOException { + Request request = variantGroup.linkContentTypes("test_content_type").request(); + Assertions.assertEquals("PUT", request.method()); + Assertions.assertEquals( + "https://api.contentstack.io/v3/variant_groups/" + VARIANT_GROUP_UID + "/variants", + request.url().toString() + ); + + Assertions.assertNotNull(request.body(), "Request body should not be null"); + Buffer buffer = new Buffer(); + request.body().writeTo(buffer); + String requestBody = buffer.readUtf8(); + + // Verify the request body contains all required fields + Assertions.assertTrue(requestBody.contains("\"uid\":\"" + VARIANT_GROUP_UID + "\""), "Request body should contain variant group UID"); + Assertions.assertTrue(requestBody.contains("\"content_types\":["), "Request body should contain content_types array"); + Assertions.assertTrue(requestBody.contains("\"uid\":\"test_content_type\""), "Request body should contain content type UID"); + Assertions.assertTrue(requestBody.contains("\"status\":\"linked\""), "Request body should contain linked status"); + Assertions.assertTrue(requestBody.contains("\"branches\":["), "Request body should contain branches array"); + Assertions.assertTrue(requestBody.contains("\"main\""), "Request body should contain main branch"); + } + + @Test + void testUnlinkContentTypes_MultipleContentTypes() throws IOException { + Request request = variantGroup.unlinkContentTypes("type1", "type2", "type3").request(); + Assertions.assertEquals("PUT", request.method()); + Assertions.assertEquals( + "https://api.contentstack.io/v3/variant_groups/" + VARIANT_GROUP_UID + "/variants", + request.url().toString() + ); + + Assertions.assertNotNull(request.body(), "Request body should not be null"); + Buffer buffer = new Buffer(); + request.body().writeTo(buffer); + String requestBody = buffer.readUtf8(); + + // Verify the request body contains all required fields + Assertions.assertTrue(requestBody.contains("\"uid\":\"" + VARIANT_GROUP_UID + "\""), "Request body should contain variant group UID"); + Assertions.assertTrue(requestBody.contains("\"content_types\":["), "Request body should contain content_types array"); + + // Verify each content type is included with unlinked status + Assertions.assertTrue(requestBody.contains("\"uid\":\"type1\""), "Request body should contain first content type"); + Assertions.assertTrue(requestBody.contains("\"uid\":\"type2\""), "Request body should contain second content type"); + Assertions.assertTrue(requestBody.contains("\"uid\":\"type3\""), "Request body should contain third content type"); + Assertions.assertTrue(requestBody.contains("\"status\":\"unlinked\""), "Request body should contain unlinked status"); + + // Verify branches + Assertions.assertTrue(requestBody.contains("\"branches\":["), "Request body should contain branches array"); + Assertions.assertTrue(requestBody.contains("\"main\""), "Request body should contain main branch"); + } + + @Test + void testFind_CallsCorrectEndpoint() throws IOException { + variantGroup.addParam("include_count", true); + Request request = variantGroup.find().request(); + Assertions.assertEquals("GET", request.method()); + Assertions.assertTrue( + request.url().toString().contains("include_count=true"), + "URL should include the added parameter" + ); + } + + @Test + void testSetBranches_WithList() throws IOException { + List testBranches = Arrays.asList("main", "development", "staging"); + variantGroup.setBranches(testBranches); + + Request request = variantGroup.linkContentTypes("test_content_type").request(); + Assertions.assertNotNull(request.body(), "Request body should not be null"); + Buffer buffer = new Buffer(); + request.body().writeTo(buffer); + String requestBody = buffer.readUtf8(); + + for (String branch : testBranches) { + Assertions.assertTrue( + requestBody.contains(branch), + "Request body should contain branch: " + branch + ); + } + } + + @Test + void testSetBranches_WithVarargs() throws IOException { + variantGroup.setBranches("main", "feature-1", "feature-2"); + + Request request = variantGroup.linkContentTypes("test_content_type").request(); + Assertions.assertNotNull(request.body(), "Request body should not be null"); + Buffer buffer = new Buffer(); + request.body().writeTo(buffer); + String requestBody = buffer.readUtf8(); + + Assertions.assertTrue(requestBody.contains("main")); + Assertions.assertTrue(requestBody.contains("feature-1")); + Assertions.assertTrue(requestBody.contains("feature-2")); + } + + @Test + void testDefaultBranch() throws IOException { + VariantGroup newGroup = TestClient.getClient().stack(API_KEY, MANAGEMENT_TOKEN).variantGroup("test_uid"); + Request request = newGroup.linkContentTypes("test_content_type").request(); + Assertions.assertNotNull(request.body(), "Request body should not be null"); + Buffer buffer = new Buffer(); + request.body().writeTo(buffer); + String requestBody = buffer.readUtf8(); + + Assertions.assertTrue(requestBody.contains("main"), "Default branch should be 'main'"); + } + +} \ No newline at end of file From c4286971e9e998b36ab352ea2020f0e3be8fe6db Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 3 Oct 2025 13:07:55 +0530 Subject: [PATCH 153/197] chore: corrections --- .../java/com/contentstack/cms/stack/VariantGroup.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/contentstack/cms/stack/VariantGroup.java b/src/main/java/com/contentstack/cms/stack/VariantGroup.java index e05d8c81..17b2f557 100644 --- a/src/main/java/com/contentstack/cms/stack/VariantGroup.java +++ b/src/main/java/com/contentstack/cms/stack/VariantGroup.java @@ -104,7 +104,7 @@ public VariantGroup setBranches(String... branches) { * @param value The "value" parameter is of type Object, which means it can * accept any type of * object as its value. - * @return instance of Asset + * @return instance of VariantGroup */ @Override public VariantGroup addParam(@NotNull String key, @NotNull Object value) { @@ -119,7 +119,7 @@ public VariantGroup addParam(@NotNull String key, @NotNull Object value) { * header. * @param value The value parameter is a string that represents the value of the * header. - * @return instance of Asset + * @return instance of VariantGroup */ @Override public VariantGroup addHeader(@NotNull String key, @NotNull String value) { @@ -132,7 +132,7 @@ public VariantGroup addHeader(@NotNull String key, @NotNull String value) { * is a String * representing the header name and the value is a String * representing the header value. - * @return instance of Asset + * @return instance of VariantGroup */ @Override public VariantGroup addHeaders(@NotNull HashMap headers) { @@ -145,7 +145,7 @@ public VariantGroup addHeaders(@NotNull HashMap headers) { * @param headers The "params" parameter is a HashMap that maps String keys to * Object values. It is * annotated with @NotNull, indicating that it cannot be null. - * @return instance of Asset + * @return instance of VariantGroup */ @Override public VariantGroup addParams(@NotNull HashMap headers) { From 4ca0c3e1e10fec5975986167243e87cf967cc023 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Thu, 9 Oct 2025 12:20:43 +0530 Subject: [PATCH 154/197] chore: version bump --- changelog.md | 6 ++++++ pom.xml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 222ab339..375a3af0 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## v1.9.0 + +### Oct 13, 2025 + +- Feature : Variant Group support + ## v1.8.0 ### Sep 15, 2025 diff --git a/pom.xml b/pom.xml index f2e0e305..0b0ceaee 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.8.0 + 1.9.0 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach From 3b477d618d832f8758ef07ed277bfdd0d9dca9de Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Thu, 23 Oct 2025 12:50:36 +0530 Subject: [PATCH 155/197] chore: version bump --- changelog.md | 6 ++++++ pom.xml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 95034af3..365536a0 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## v1.10.0 + +### Oct 27, 2025 + +- Feature : TOTP support + ## v1.9.0 ### Oct 13, 2025 diff --git a/pom.xml b/pom.xml index 4a401af9..11e2b793 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.9.0 + 1.10.0 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach From f1cc38b743d1db77f9520ed35ae8aba26d0131aa Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Thu, 4 Dec 2025 09:50:39 +0530 Subject: [PATCH 156/197] feat: Introduce centralized error messages for improved consistency and maintainability across the codebase --- .../contentstack/cms/core/ErrorMessages.java | 51 +++++++++++++++++++ .../java/com/contentstack/cms/core/Util.java | 39 +++++++------- .../cms/organization/Organization.java | 4 +- .../com/contentstack/cms/stack/Alias.java | 6 ++- .../com/contentstack/cms/stack/Asset.java | 26 +++++----- .../com/contentstack/cms/stack/AuditLog.java | 4 +- .../com/contentstack/cms/stack/Branch.java | 4 +- .../contentstack/cms/stack/DeliveryToken.java | 4 +- .../com/contentstack/cms/stack/Entry.java | 4 +- .../contentstack/cms/stack/Environment.java | 6 ++- .../contentstack/cms/stack/Extensions.java | 4 +- .../contentstack/cms/stack/FileUploader.java | 4 +- .../com/contentstack/cms/stack/Folder.java | 4 +- .../contentstack/cms/stack/GlobalField.java | 4 +- .../com/contentstack/cms/stack/Label.java | 4 +- .../com/contentstack/cms/stack/Locale.java | 4 +- .../cms/stack/ManagementToken.java | 4 +- .../contentstack/cms/stack/PublishQueue.java | 4 +- .../com/contentstack/cms/stack/Release.java | 4 +- .../contentstack/cms/stack/ReleaseItem.java | 4 +- .../com/contentstack/cms/stack/Roles.java | 4 +- .../com/contentstack/cms/stack/Terms.java | 4 +- .../contentstack/cms/stack/VariantGroup.java | 4 +- .../com/contentstack/cms/stack/Webhook.java | 4 +- .../com/contentstack/cms/stack/Workflow.java | 6 ++- .../com/contentstack/cms/core/TestUtils.java | 4 +- 26 files changed, 156 insertions(+), 58 deletions(-) create mode 100644 src/main/java/com/contentstack/cms/core/ErrorMessages.java diff --git a/src/main/java/com/contentstack/cms/core/ErrorMessages.java b/src/main/java/com/contentstack/cms/core/ErrorMessages.java new file mode 100644 index 00000000..89b64cdc --- /dev/null +++ b/src/main/java/com/contentstack/cms/core/ErrorMessages.java @@ -0,0 +1,51 @@ +package com.contentstack.cms.core; + +public final class ErrorMessages { + + private ErrorMessages() { + throw new AssertionError("This class cannot be instantiated. Use the static constants to continue."); + } + + public static final String NOT_LOGGED_IN = "You are not logged in. Login to continue."; + public static final String ALREADY_LOGGED_IN = "Operation not allowed. You are already logged in."; + public static final String OAUTH_LOGIN_REQUIRED = "Login or configure OAuth to continue."; + public static final String OAUTH_NO_TOKENS = "No OAuth tokens available. Please authenticate first."; + public static final String OAUTH_NO_REFRESH_TOKEN = "No refresh token available"; + public static final String OAUTH_EMPTY_CODE = "Authorization code cannot be null or empty"; + public static final String OAUTH_CONFIG_MISSING = "OAuth is not configured. Use Builder.setOAuth() with or without clientSecret for PKCE flow"; + public static final String OAUTH_REFRESH_FAILED = "Failed to refresh access token"; + public static final String OAUTH_REVOKE_FAILED = "Failed to revoke authorization"; + public static final String OAUTH_STATUS_FAILED = "Failed to get authorization status"; + public static final String OAUTH_ORG_EMPTY = "organizationUid can not be empty"; + + public static final String PRIVATE_CONSTRUCTOR = "This class cannot be instantiated. Use the static methods to continue."; + public static final String PRIVATE_CONSTRUCTOR_DETAILED = "This private constructor cannot be accessed outside the class. Use the public methods instead."; + + public static final String ASSET_UID_REQUIRED = "Asset UID is required. Provide a valid Asset UID and try again."; + public static final String LOG_ITEM_UID_REQUIRED = "Log Item UID is required. Provide a valid Log Item UID and try again."; + public static final String BRANCH_UID_REQUIRED = "Branch UID is required. Provide a valid Branch UID and try again."; + public static final String CUSTOM_FIELD_UID_REQUIRED = "Custom Field UID is required. Provide a valid Custom Field UID and try again."; + public static final String DELIVERY_TOKEN_UID_REQUIRED = "Delivery Token UID is required. Provide a valid Delivery Token UID and try again."; + public static final String ENVIRONMENT_REQUIRED = "Environment is required. Provide a valid Environment and try again."; + public static final String FOLDER_UID_REQUIRED = "Folder UID is required. Provide a valid Folder UID and try again."; + public static final String GLOBAL_FIELD_UID_REQUIRED = "Global Field UID is required. Provide a valid Global Field UID and try again."; + public static final String LABEL_UID_REQUIRED = "Label UID is required. Provide a valid Label UID and try again."; + public static final String LOCALE_CODE_REQUIRED = "Locale Code is required. Provide a valid Locale Code and try again."; + public static final String MANAGEMENT_TOKEN_UID_REQUIRED = "Management Token UID is required. Provide a valid Management Token UID and try again."; + public static final String ORGANIZATION_UID_REQUIRED = "Organization UID is required. Provide a valid Organization UID and try again."; + public static final String PUBLISH_QUEUE_UID_REQUIRED = "Publish Queue UID is required. Provide a valid Publish Queue UID and try again."; + public static final String RELEASE_UID_REQUIRED = "Release UID is required. Provide a valid Release UID and try again."; + public static final String ROLE_UID_REQUIRED = "Role UID is required. Provide a valid Role UID and try again."; + public static final String VARIANT_GROUP_UID_REQUIRED = "Variant Group UID is required. Provide a valid Variant Group UID and try again."; + public static final String WEBHOOK_UID_REQUIRED = "Webhook UID is required. Provide a valid Webhook UID and try again."; + public static final String WORKFLOW_UID_REQUIRED = "Workflow UID is required. Provide a valid Workflow UID and try again."; + + public static final String CONTENT_TYPE_REQUIRED = "Content Type is required. Provide a valid Content Type and try again."; + public static final String REFERENCE_FIELDS_INVALID = "Reference Fields must be a string or an array of strings. Provide valid values and try again."; + public static final String TERM_STRING_REQUIRED = "Term String is required. Provide a valid Term String and try again."; + + public static final String FILE_CONTENT_TYPE_UNKNOWN = "The file's content type could not be determined. Provide a valid file and try again."; + + public static final String MISSING_INSTALLATION_ID = "installation uid is required"; + public static final String MISSING_ORG_ID = "organization uid is required"; +} diff --git a/src/main/java/com/contentstack/cms/core/Util.java b/src/main/java/com/contentstack/cms/core/Util.java index a07b302e..f3434201 100644 --- a/src/main/java/com/contentstack/cms/core/Util.java +++ b/src/main/java/com/contentstack/cms/core/Util.java @@ -20,7 +20,7 @@ public class Util { // "1.2.0". public static final String SDK_VERSION = "1.3.1"; - static final String PRIVATE_CONSTRUCTOR = "private constructor can't be accessed outside the class"; + static final String PRIVATE_CONSTRUCTOR = ErrorMessages.PRIVATE_CONSTRUCTOR; public static final Boolean RETRY_ON_FAILURE = true; public static final String PROTOCOL = "https"; public static final String HOST = "api.contentstack.io"; @@ -28,11 +28,10 @@ public class Util { public static final String VERSION = "v3"; public static final int TIMEOUT = 30; public static final String SDK_NAME = "contentstack-management-java"; - public static final String ILLEGAL_USER = "Please Login to access stack instance"; - public static final String USER_ALREADY_LOGGED_IN = "User is already loggedIn, " - + "Please logout then try to login again"; - public static final String LOGIN_FLAG = "Please login to access user instance"; - public static final String PLEASE_LOGIN = "Please Login to access stack instance"; + public static final String ILLEGAL_USER = ErrorMessages.NOT_LOGGED_IN; + public static final String USER_ALREADY_LOGGED_IN = ErrorMessages.ALREADY_LOGGED_IN; + public static final String LOGIN_FLAG = ErrorMessages.NOT_LOGGED_IN; + public static final String PLEASE_LOGIN = ErrorMessages.NOT_LOGGED_IN; // CONSTANT KEYS public static final String API_KEY = "api_key"; @@ -47,9 +46,9 @@ public class Util { public static final String CONTENT_TYPE_VALUE = "application/json"; // Error Messages - public static final String MISSING_INSTALLATION_ID = "installation uid is required"; - public static final String ERROR_INSTALLATION = "installation uid is required"; - public static final String MISSING_ORG_ID = "organization uid is required"; + public static final String MISSING_INSTALLATION_ID = ErrorMessages.MISSING_INSTALLATION_ID; + public static final String ERROR_INSTALLATION = ErrorMessages.MISSING_INSTALLATION_ID; + public static final String MISSING_ORG_ID = ErrorMessages.MISSING_ORG_ID; // OAuth Constants public static final String OAUTH_APP_HOST = "app.contentstack.com"; @@ -58,15 +57,15 @@ public class Util { public static final String OAUTH_AUTHORIZE_ENDPOINT = "/#!/apps/%s/authorize"; // OAuth Error Messages - public static final String OAUTH_NO_TOKENS = "No OAuth tokens available. Please authenticate first."; - public static final String OAUTH_NO_REFRESH_TOKEN = "No refresh token available"; - public static final String OAUTH_EMPTY_CODE = "Authorization code cannot be null or empty"; - public static final String OAUTH_CONFIG_MISSING = "OAuth is not configured. Use Builder.setOAuth() with or without clientSecret for PKCE flow"; - public static final String OAUTH_REFRESH_FAILED = "Failed to refresh access token"; - public static final String OAUTH_REVOKE_FAILED = "Failed to revoke authorization"; - public static final String OAUTH_STATUS_FAILED = "Failed to get authorization status"; - public static final String OAUTH_LOGIN_REQUIRED = "Please login or configure OAuth to access"; - public static final String OAUTH_ORG_EMPTY = "organizationUid can not be empty"; + public static final String OAUTH_NO_TOKENS = ErrorMessages.OAUTH_NO_TOKENS; + public static final String OAUTH_NO_REFRESH_TOKEN = ErrorMessages.OAUTH_NO_REFRESH_TOKEN; + public static final String OAUTH_EMPTY_CODE = ErrorMessages.OAUTH_EMPTY_CODE; + public static final String OAUTH_CONFIG_MISSING = ErrorMessages.OAUTH_CONFIG_MISSING; + public static final String OAUTH_REFRESH_FAILED = ErrorMessages.OAUTH_REFRESH_FAILED; + public static final String OAUTH_REVOKE_FAILED = ErrorMessages.OAUTH_REVOKE_FAILED; + public static final String OAUTH_STATUS_FAILED = ErrorMessages.OAUTH_STATUS_FAILED; + public static final String OAUTH_LOGIN_REQUIRED = ErrorMessages.OAUTH_LOGIN_REQUIRED; + public static final String OAUTH_ORG_EMPTY = ErrorMessages.OAUTH_ORG_EMPTY; // The code `Util() throws IllegalAccessException` is a constructor for the // `Util` class that throws an @@ -76,7 +75,7 @@ public class Util { // `IllegalAccessException` is to prevent // the instantiation of the `Util` class from outside the class itself. Util() throws IllegalAccessException { - throw new IllegalAccessException("private=modifier"); + throw new IllegalAccessException(ErrorMessages.PRIVATE_CONSTRUCTOR_DETAILED); } /** @@ -109,7 +108,7 @@ public static void nullEmptyThrowsException(@NotNull String field) { try { throw new CMARuntimeException(field + " cannot take in an empty String or null value"); } catch (CMARuntimeException e) { - System.out.println("Exception: " + e.getLocalizedMessage()); + System.out.println("An error occurred due to " + e.getLocalizedMessage() + "."); } } diff --git a/src/main/java/com/contentstack/cms/organization/Organization.java b/src/main/java/com/contentstack/cms/organization/Organization.java index dfdae44d..58321d3e 100644 --- a/src/main/java/com/contentstack/cms/organization/Organization.java +++ b/src/main/java/com/contentstack/cms/organization/Organization.java @@ -1,5 +1,7 @@ package com.contentstack.cms.organization; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -26,7 +28,7 @@ public class Organization implements BaseImplementation { protected HashMap headers; protected HashMap params; private String organizationUid; - final String ERROR_MSG = "OrganizationUid Can Not Be Null OR Empty"; + final String ERROR_MSG = ErrorMessages.ORGANIZATION_UID_REQUIRED; /** diff --git a/src/main/java/com/contentstack/cms/stack/Alias.java b/src/main/java/com/contentstack/cms/stack/Alias.java index c074fc1d..499e1275 100644 --- a/src/main/java/com/contentstack/cms/stack/Alias.java +++ b/src/main/java/com/contentstack/cms/stack/Alias.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -160,7 +162,7 @@ public Call find() { * @since 2022-10-20 */ public Call fetch() { - Objects.requireNonNull(this.uid, "Global Field Uid can not be null or empty"); + Objects.requireNonNull(this.uid, "ErrorMessages.GLOBAL_FIELD_UID_REQUIRED"); return this.service.single(this.headers, this.uid); } @@ -206,7 +208,7 @@ public Call update(@NotNull JSONObject body) { * @since 2022-10-20 */ public Call delete() { - Objects.requireNonNull(this.uid, "Global Field Uid can not be null or empty"); + Objects.requireNonNull(this.uid, "ErrorMessages.GLOBAL_FIELD_UID_REQUIRED"); return this.service.delete(this.headers, this.uid, this.params); } diff --git a/src/main/java/com/contentstack/cms/stack/Asset.java b/src/main/java/com/contentstack/cms/stack/Asset.java index a2ee4a1c..c0502978 100644 --- a/src/main/java/com/contentstack/cms/stack/Asset.java +++ b/src/main/java/com/contentstack/cms/stack/Asset.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.MediaType; import okhttp3.MultipartBody; @@ -225,12 +227,12 @@ public Call findAsPojo() { * @since 2022-10-20 */ public Call fetch() { - Objects.requireNonNull(this.assetUid, "Asset Uid Can Not Be Null OR Empty"); + Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); return this.service.single(this.headers, this.assetUid, this.params); } public Call fetchAsPojo() { // New method for POJO conversion - Objects.requireNonNull(this.assetUid, "Asset Uid Can Not Be Null OR Empty"); + Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); return this.service.singlePojo(this.headers, this.assetUid, this.params); } @@ -390,7 +392,7 @@ private MultipartBody createMultipartBody(String filePath, String parentUid, Str * @since 2022-10-20 */ public Call replace(@NotNull String filePath, @NotNull String description) { - Objects.requireNonNull(this.assetUid, "Asset Uid Can Not Be Null OR Empty"); + Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); MultipartBody.Part assetPath = uploadFile(filePath); RequestBody body = RequestBody.create(Objects.requireNonNull(MediaType.parse(String.valueOf(MultipartBody.FORM))), description); return this.service.replace(this.headers, this.assetUid, assetPath, body, this.params); @@ -443,7 +445,7 @@ private MultipartBody.Part uploadFile(@NotNull String filePath) { * @since 2022-10-20 */ public Call generatePermanentUrl(JSONObject body) { - Objects.requireNonNull(this.assetUid, "Asset Uid Can Not Be Null OR Empty"); + Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); return this.service.generatePermanentUrl(this.headers, this.assetUid, body); } @@ -473,7 +475,7 @@ public Call generatePermanentUrl(JSONObject body) { * @since 2022-10-20 */ public Call getPermanentUrl(String slugUrl) { - Objects.requireNonNull(this.assetUid, "Asset Uid Can Not Be Null OR Empty"); + Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); return this.service.downloadPermanentUrl(this.headers, this.assetUid, slugUrl, this.params); } @@ -490,7 +492,7 @@ public Call getPermanentUrl(String slugUrl) { * @since 0.1.0 */ public Call delete() { - Objects.requireNonNull(this.assetUid, "Asset Uid Can Not Be Null OR Empty"); + Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); return this.service.delete(this.headers, this.assetUid); } @@ -580,7 +582,7 @@ public Call setVersionName(int versionNumber, * @since 0.1.0 */ public Call getVersionNameDetails() { - Objects.requireNonNull(this.assetUid, "Asset Uid Can Not Be Null OR Empty"); + Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); return this.service.getVersionNameDetails(this.headers, this.assetUid, this.params); } @@ -601,7 +603,7 @@ public Call getVersionNameDetails() { * @since 0.1.0 */ public Call deleteVersionName(int versionNumber) { - Objects.requireNonNull(this.assetUid, "Asset Uid Can Not Be Null OR Empty"); + Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); return this.service.deleteVersionName(this.headers, this.assetUid, versionNumber); } @@ -618,7 +620,7 @@ public Call deleteVersionName(int versionNumber) { * @since 0.1.0 */ public Call getReferences() { - Objects.requireNonNull(this.assetUid, "Asset Uid Can Not Be Null OR Empty"); + Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); return this.service.getReferences(this.headers, this.assetUid); } @@ -676,7 +678,7 @@ public Call getByType(@NotNull String assetType) { * @since 0.1.0 */ public Call updateDetails(JSONObject requestBody) { - Objects.requireNonNull(this.assetUid, "Asset Uid Can Not Be Null OR Empty"); + Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); return this.service.updateDetails(this.headers, this.assetUid, this.params, requestBody); } @@ -702,7 +704,7 @@ public Call updateDetails(JSONObject requestBody) { * @since 0.1.0 */ public Call publish(@NotNull JSONObject requestBody) { - Objects.requireNonNull(this.assetUid, "Asset Uid Can Not Be Null OR Empty"); + Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); return this.service.publish(this.headers, this.assetUid, requestBody); } @@ -728,7 +730,7 @@ public Call publish(@NotNull JSONObject requestBody) { */ public Call unpublish( @NotNull JSONObject requestBody) { - Objects.requireNonNull(this.assetUid, "Asset Uid Can Not Be Null OR Empty"); + Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); return this.service.unpublish(this.headers, this.assetUid, requestBody); } diff --git a/src/main/java/com/contentstack/cms/stack/AuditLog.java b/src/main/java/com/contentstack/cms/stack/AuditLog.java index d7ce42d3..2ff0c188 100644 --- a/src/main/java/com/contentstack/cms/stack/AuditLog.java +++ b/src/main/java/com/contentstack/cms/stack/AuditLog.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -112,7 +114,7 @@ public Call find() { * @return Call */ public Call fetch() { - Objects.requireNonNull(this.logItemUid, "Log Item uid can not be null or empty"); + Objects.requireNonNull(this.logItemUid, "ErrorMessages.LOG_ITEM_UID_REQUIRED"); return this.service.fetch(this.headers, this.logItemUid); } diff --git a/src/main/java/com/contentstack/cms/stack/Branch.java b/src/main/java/com/contentstack/cms/stack/Branch.java index f4572d57..fdf93b52 100644 --- a/src/main/java/com/contentstack/cms/stack/Branch.java +++ b/src/main/java/com/contentstack/cms/stack/Branch.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -53,7 +55,7 @@ protected Branch(Retrofit instance,Map headers, String uid) { } void validate() { - final String ERROR_MESSAGE = "The Branch UID Can Not Be Null ORr Empty"; + final String ERROR_MESSAGE = ErrorMessages.BRANCH_UID_REQUIRED; Objects.requireNonNull(this.baseBranchId, ERROR_MESSAGE); if (this.baseBranchId.isEmpty()) throw new IllegalStateException(ERROR_MESSAGE); diff --git a/src/main/java/com/contentstack/cms/stack/DeliveryToken.java b/src/main/java/com/contentstack/cms/stack/DeliveryToken.java index f0aa8cc5..7e5e6d8e 100644 --- a/src/main/java/com/contentstack/cms/stack/DeliveryToken.java +++ b/src/main/java/com/contentstack/cms/stack/DeliveryToken.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -30,7 +32,7 @@ public class DeliveryToken implements BaseImplementation { protected HashMap headers; protected HashMap params; private String tokenUid; - String ERROR = "Token UID Can Not Be Null OR Empty"; + String ERROR = ErrorMessages.DELIVERY_TOKEN_UID_REQUIRED; protected DeliveryToken(TokenService service, Map headers) { this.headers = new HashMap<>(); diff --git a/src/main/java/com/contentstack/cms/stack/Entry.java b/src/main/java/com/contentstack/cms/stack/Entry.java index 818229a9..1f74c5dc 100644 --- a/src/main/java/com/contentstack/cms/stack/Entry.java +++ b/src/main/java/com/contentstack/cms/stack/Entry.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -145,7 +147,7 @@ public Call includeReference(@NotNull Object referenceField){ if (referenceField instanceof String || referenceField instanceof String[]) { addToParams("include[]", referenceField); } else { - throw new IllegalArgumentException("Reference fields must be a String or an array of Strings"); + throw new IllegalArgumentException(ErrorMessages.REFERENCE_FIELDS_INVALID); } validateCT(); return this.service.fetch(this.headers, this.contentTypeUid, this.params); diff --git a/src/main/java/com/contentstack/cms/stack/Environment.java b/src/main/java/com/contentstack/cms/stack/Environment.java index 8ef0c9d2..e2e16841 100644 --- a/src/main/java/com/contentstack/cms/stack/Environment.java +++ b/src/main/java/com/contentstack/cms/stack/Environment.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -43,10 +45,10 @@ protected Environment(Retrofit instance, Map headers, String env } void validate() { - String ERROR = "Environment Can Not Be Null OR Empty"; + String ERROR = ErrorMessages.ENVIRONMENT_REQUIRED; Objects.requireNonNull(this.environment, ERROR); if (this.environment.isEmpty()) - throw new IllegalStateException("Environment can not be null or empty"); + throw new IllegalStateException(ErrorMessages.ENVIRONMENT_REQUIRED); } /** diff --git a/src/main/java/com/contentstack/cms/stack/Extensions.java b/src/main/java/com/contentstack/cms/stack/Extensions.java index 437c8cab..f1d619c1 100644 --- a/src/main/java/com/contentstack/cms/stack/Extensions.java +++ b/src/main/java/com/contentstack/cms/stack/Extensions.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import com.contentstack.cms.core.Util; import okhttp3.RequestBody; @@ -53,7 +55,7 @@ protected Extensions(Retrofit retrofit,Map headers, String field void validate() { if (this.customFieldUid == null || this.customFieldUid.isEmpty()) - throw new IllegalAccessError("Custom Field UID Can Not Be Null OR Empty"); + throw new IllegalAccessError(ErrorMessages.CUSTOM_FIELD_UID_REQUIRED); } diff --git a/src/main/java/com/contentstack/cms/stack/FileUploader.java b/src/main/java/com/contentstack/cms/stack/FileUploader.java index 84cd9f30..522b5328 100644 --- a/src/main/java/com/contentstack/cms/stack/FileUploader.java +++ b/src/main/java/com/contentstack/cms/stack/FileUploader.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.RequestBody; @@ -81,7 +83,7 @@ private String getContentType(File file) { javax.activation.MimetypesFileTypeMap m = new javax.activation.MimetypesFileTypeMap(source.toString()); return m.getContentType(file); } catch (IOException e) { - throw new RuntimeException("Failed to determine content type of file", e); + throw new RuntimeException(ErrorMessages.FILE_CONTENT_TYPE_UNKNOWN, e); } } diff --git a/src/main/java/com/contentstack/cms/stack/Folder.java b/src/main/java/com/contentstack/cms/stack/Folder.java index a249b598..1a89438f 100644 --- a/src/main/java/com/contentstack/cms/stack/Folder.java +++ b/src/main/java/com/contentstack/cms/stack/Folder.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -35,7 +37,7 @@ protected Folder(Retrofit instance,Map headers, String folderUid void validate() { if (this.folderUid == null || this.folderUid.isEmpty()) - throw new IllegalAccessError("Folder uid uid can not be null or empty"); + throw new IllegalAccessError(ErrorMessages.FOLDER_UID_REQUIRED); } /** diff --git a/src/main/java/com/contentstack/cms/stack/GlobalField.java b/src/main/java/com/contentstack/cms/stack/GlobalField.java index 681e2e16..6f05371a 100644 --- a/src/main/java/com/contentstack/cms/stack/GlobalField.java +++ b/src/main/java/com/contentstack/cms/stack/GlobalField.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -55,7 +57,7 @@ protected GlobalField(Retrofit retrofit,Map headers, String uid) void validate() { if (this.globalFiledUid == null) - throw new IllegalAccessError("Global Field Uid can not be null or empty"); + throw new IllegalAccessError(ErrorMessages.GLOBAL_FIELD_UID_REQUIRED); } diff --git a/src/main/java/com/contentstack/cms/stack/Label.java b/src/main/java/com/contentstack/cms/stack/Label.java index d6b3943c..e550b041 100644 --- a/src/main/java/com/contentstack/cms/stack/Label.java +++ b/src/main/java/com/contentstack/cms/stack/Label.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -50,7 +52,7 @@ protected Label(Retrofit retrofit,Map headers, String labelUid) void validate() { if (this.labelUid == null || this.labelUid.isEmpty()) - throw new IllegalAccessError("Label Uid can not be null or empty"); + throw new IllegalAccessError(ErrorMessages.LABEL_UID_REQUIRED); } @Override diff --git a/src/main/java/com/contentstack/cms/stack/Locale.java b/src/main/java/com/contentstack/cms/stack/Locale.java index e8231b09..64946e66 100644 --- a/src/main/java/com/contentstack/cms/stack/Locale.java +++ b/src/main/java/com/contentstack/cms/stack/Locale.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -54,7 +56,7 @@ public Locale(Retrofit client,Map headers, String code) { } void validate() { - if (this.code == null) throw new IllegalAccessError("The Locale Code Can Not Be Null OR Empty"); + if (this.code == null) throw new IllegalAccessError(ErrorMessages.LOCALE_CODE_REQUIRED); } /** diff --git a/src/main/java/com/contentstack/cms/stack/ManagementToken.java b/src/main/java/com/contentstack/cms/stack/ManagementToken.java index dda8a3eb..a8a12ec6 100644 --- a/src/main/java/com/contentstack/cms/stack/ManagementToken.java +++ b/src/main/java/com/contentstack/cms/stack/ManagementToken.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -42,7 +44,7 @@ protected ManagementToken(TokenService service,Map headers, Stri void validate() { if (this.tokenUid == null || this.tokenUid.isEmpty()) - throw new IllegalAccessError("Token uid Can Not Be Null OR Empty"); + throw new IllegalAccessError(ErrorMessages.MANAGEMENT_TOKEN_UID_REQUIRED); } diff --git a/src/main/java/com/contentstack/cms/stack/PublishQueue.java b/src/main/java/com/contentstack/cms/stack/PublishQueue.java index 0da7c651..9873a3ec 100644 --- a/src/main/java/com/contentstack/cms/stack/PublishQueue.java +++ b/src/main/java/com/contentstack/cms/stack/PublishQueue.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -64,7 +66,7 @@ protected PublishQueue(Retrofit retrofit,Map headers, String uid void validate() { if (this.publishQueueUid == null || this.publishQueueUid.isEmpty()) - throw new IllegalAccessError("Publish Queue Uid can not be null or empty"); + throw new IllegalAccessError(ErrorMessages.PUBLISH_QUEUE_UID_REQUIRED); } diff --git a/src/main/java/com/contentstack/cms/stack/Release.java b/src/main/java/com/contentstack/cms/stack/Release.java index bba9bbdb..6c53a390 100644 --- a/src/main/java/com/contentstack/cms/stack/Release.java +++ b/src/main/java/com/contentstack/cms/stack/Release.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -58,7 +60,7 @@ protected Release(Retrofit retrofit,Map headers, String uid) { void validate() { if (this.releaseUid == null || this.releaseUid.isEmpty()) - throw new IllegalAccessError("Release Uid Can Not Be Null OR Empty"); + throw new IllegalAccessError(ErrorMessages.RELEASE_UID_REQUIRED); } diff --git a/src/main/java/com/contentstack/cms/stack/ReleaseItem.java b/src/main/java/com/contentstack/cms/stack/ReleaseItem.java index 8dbf44f7..00ff92d9 100644 --- a/src/main/java/com/contentstack/cms/stack/ReleaseItem.java +++ b/src/main/java/com/contentstack/cms/stack/ReleaseItem.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -40,7 +42,7 @@ protected ReleaseItem(Retrofit retrofit, Map headers, String rel void validate() { if (this.releaseUid == null || this.releaseUid.isEmpty()) - throw new IllegalAccessError("Release Uid can not be null or empty"); + throw new IllegalAccessError(ErrorMessages.RELEASE_UID_REQUIRED); } /** diff --git a/src/main/java/com/contentstack/cms/stack/Roles.java b/src/main/java/com/contentstack/cms/stack/Roles.java index a3a51404..9d10ac6e 100644 --- a/src/main/java/com/contentstack/cms/stack/Roles.java +++ b/src/main/java/com/contentstack/cms/stack/Roles.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -45,7 +47,7 @@ protected Roles(Retrofit retrofit,Map headers, String roleUid) { void validate() { if (this.roleUid == null || this.roleUid.isEmpty()) - throw new IllegalAccessError("Role uid can not be null or empty"); + throw new IllegalAccessError(ErrorMessages.ROLE_UID_REQUIRED); } diff --git a/src/main/java/com/contentstack/cms/stack/Terms.java b/src/main/java/com/contentstack/cms/stack/Terms.java index e9e2a154..8f3a3a0c 100644 --- a/src/main/java/com/contentstack/cms/stack/Terms.java +++ b/src/main/java/com/contentstack/cms/stack/Terms.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -289,7 +291,7 @@ public Call reorder(@NotNull String termUid, @NotNull JSONObject b */ public Call search(@NotNull String termString) { if (termString.isEmpty()) { - throw new IllegalArgumentException("termString must not be empty"); + throw new IllegalArgumentException(ErrorMessages.TERM_STRING_REQUIRED); } return this.taxonomyService.searchTerm(this.headers, termString); } diff --git a/src/main/java/com/contentstack/cms/stack/VariantGroup.java b/src/main/java/com/contentstack/cms/stack/VariantGroup.java index 17b2f557..87b8dc03 100644 --- a/src/main/java/com/contentstack/cms/stack/VariantGroup.java +++ b/src/main/java/com/contentstack/cms/stack/VariantGroup.java @@ -1,4 +1,6 @@ package com.contentstack.cms.stack; + +import com.contentstack.cms.core.ErrorMessages; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -69,7 +71,7 @@ protected VariantGroup(Retrofit instance, Map headers, String va */ void validate() { if (this.variantGroupUid == null || this.variantGroupUid.isEmpty()) - throw new IllegalAccessError("Variant group uid can not be null or empty"); + throw new IllegalAccessError(ErrorMessages.VARIANT_GROUP_UID_REQUIRED); } /** diff --git a/src/main/java/com/contentstack/cms/stack/Webhook.java b/src/main/java/com/contentstack/cms/stack/Webhook.java index d360ffbe..c2034e2c 100644 --- a/src/main/java/com/contentstack/cms/stack/Webhook.java +++ b/src/main/java/com/contentstack/cms/stack/Webhook.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import com.contentstack.cms.core.Util; import okhttp3.MediaType; @@ -56,7 +58,7 @@ protected Webhook(Retrofit retrofit,Map headers, String uid) { void validate() { if (this.webhookUid == null || this.webhookUid.isEmpty()) - throw new IllegalAccessError("Webhook uid can not be null or empty"); + throw new IllegalAccessError(ErrorMessages.WEBHOOK_UID_REQUIRED); } diff --git a/src/main/java/com/contentstack/cms/stack/Workflow.java b/src/main/java/com/contentstack/cms/stack/Workflow.java index c43fde9e..a62d9d92 100644 --- a/src/main/java/com/contentstack/cms/stack/Workflow.java +++ b/src/main/java/com/contentstack/cms/stack/Workflow.java @@ -1,5 +1,7 @@ package com.contentstack.cms.stack; +import com.contentstack.cms.core.ErrorMessages; + import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; @@ -49,7 +51,7 @@ protected Workflow(Retrofit retrofit,Map headers, @NotNull Strin void validate() { if (this.workflowUid == null || this.workflowUid.isEmpty()) - throw new IllegalAccessError("Workflow uid can not be null or empty"); + throw new IllegalAccessError(ErrorMessages.WORKFLOW_UID_REQUIRED); } @@ -375,7 +377,7 @@ public Call fetchPublishRule(@NotNull String ruleUid) { */ public Call fetchPublishRuleContentType(@NotNull String contentTypeUid) { if (contentTypeUid.isEmpty()) { - throw new IllegalArgumentException("Content Type can not be empty"); + throw new IllegalArgumentException(ErrorMessages.CONTENT_TYPE_REQUIRED); } return this.service.fetchPublishRuleContentType(this.headers, contentTypeUid, this.params); } diff --git a/src/test/java/com/contentstack/cms/core/TestUtils.java b/src/test/java/com/contentstack/cms/core/TestUtils.java index 4dd0ccb0..76befd90 100644 --- a/src/test/java/com/contentstack/cms/core/TestUtils.java +++ b/src/test/java/com/contentstack/cms/core/TestUtils.java @@ -1,5 +1,7 @@ package com.contentstack.cms.core; +import static com.contentstack.cms.core.ErrorMessages.PRIVATE_CONSTRUCTOR; + import org.jetbrains.annotations.Contract; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Tag; @@ -61,7 +63,7 @@ void testAssertionError() { Util.assertionError(); } catch (AssertionError e) { Assertions.assertEquals( - "private constructor can't be accessed outside the class", + PRIVATE_CONSTRUCTOR, e.getMessage()); } } From 912dfb1026ff1449571fafa85314b9ecbf93c97c Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Thu, 4 Dec 2025 10:12:23 +0530 Subject: [PATCH 157/197] refactor: Update error message handling in Alias, Asset, and AuditLog classes to use centralized error messages for consistency --- .../contentstack/cms/core/ErrorMessages.java | 2 +- .../java/com/contentstack/cms/core/Util.java | 2 +- .../com/contentstack/cms/stack/Alias.java | 19 ++++++++------- .../com/contentstack/cms/stack/Asset.java | 24 +++++++++---------- .../com/contentstack/cms/stack/AuditLog.java | 2 +- .../com/contentstack/cms/core/TestUtils.java | 2 +- 6 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/contentstack/cms/core/ErrorMessages.java b/src/main/java/com/contentstack/cms/core/ErrorMessages.java index 89b64cdc..477809fa 100644 --- a/src/main/java/com/contentstack/cms/core/ErrorMessages.java +++ b/src/main/java/com/contentstack/cms/core/ErrorMessages.java @@ -19,8 +19,8 @@ private ErrorMessages() { public static final String OAUTH_ORG_EMPTY = "organizationUid can not be empty"; public static final String PRIVATE_CONSTRUCTOR = "This class cannot be instantiated. Use the static methods to continue."; - public static final String PRIVATE_CONSTRUCTOR_DETAILED = "This private constructor cannot be accessed outside the class. Use the public methods instead."; + public static final String ALIAS_UID_REQUIRED = "Alias UID is required. Provide a valid Alias UID and try again."; public static final String ASSET_UID_REQUIRED = "Asset UID is required. Provide a valid Asset UID and try again."; public static final String LOG_ITEM_UID_REQUIRED = "Log Item UID is required. Provide a valid Log Item UID and try again."; public static final String BRANCH_UID_REQUIRED = "Branch UID is required. Provide a valid Branch UID and try again."; diff --git a/src/main/java/com/contentstack/cms/core/Util.java b/src/main/java/com/contentstack/cms/core/Util.java index f3434201..9e2d5130 100644 --- a/src/main/java/com/contentstack/cms/core/Util.java +++ b/src/main/java/com/contentstack/cms/core/Util.java @@ -75,7 +75,7 @@ public class Util { // `IllegalAccessException` is to prevent // the instantiation of the `Util` class from outside the class itself. Util() throws IllegalAccessException { - throw new IllegalAccessException(ErrorMessages.PRIVATE_CONSTRUCTOR_DETAILED); + throw new IllegalAccessException(ErrorMessages.PRIVATE_CONSTRUCTOR); } /** diff --git a/src/main/java/com/contentstack/cms/stack/Alias.java b/src/main/java/com/contentstack/cms/stack/Alias.java index 499e1275..3818aac7 100644 --- a/src/main/java/com/contentstack/cms/stack/Alias.java +++ b/src/main/java/com/contentstack/cms/stack/Alias.java @@ -1,18 +1,19 @@ package com.contentstack.cms.stack; -import com.contentstack.cms.core.ErrorMessages; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; -import com.contentstack.cms.BaseImplementation; -import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; + +import com.contentstack.cms.BaseImplementation; +import com.contentstack.cms.core.ErrorMessages; + +import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Retrofit; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - /** * An alias acts as a pointer to a particular branch. You can specify the alias * ID in your frontend code to pull content @@ -162,7 +163,7 @@ public Call find() { * @since 2022-10-20 */ public Call fetch() { - Objects.requireNonNull(this.uid, "ErrorMessages.GLOBAL_FIELD_UID_REQUIRED"); + Objects.requireNonNull(this.uid, ErrorMessages.ALIAS_UID_REQUIRED); return this.service.single(this.headers, this.uid); } @@ -208,7 +209,7 @@ public Call update(@NotNull JSONObject body) { * @since 2022-10-20 */ public Call delete() { - Objects.requireNonNull(this.uid, "ErrorMessages.GLOBAL_FIELD_UID_REQUIRED"); + Objects.requireNonNull(this.uid, ErrorMessages.ALIAS_UID_REQUIRED); return this.service.delete(this.headers, this.uid, this.params); } diff --git a/src/main/java/com/contentstack/cms/stack/Asset.java b/src/main/java/com/contentstack/cms/stack/Asset.java index c0502978..6a0d5021 100644 --- a/src/main/java/com/contentstack/cms/stack/Asset.java +++ b/src/main/java/com/contentstack/cms/stack/Asset.java @@ -227,12 +227,12 @@ public Call findAsPojo() { * @since 2022-10-20 */ public Call fetch() { - Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); + Objects.requireNonNull(this.assetUid, ErrorMessages.ASSET_UID_REQUIRED); return this.service.single(this.headers, this.assetUid, this.params); } public Call fetchAsPojo() { // New method for POJO conversion - Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); + Objects.requireNonNull(this.assetUid, ErrorMessages.ASSET_UID_REQUIRED); return this.service.singlePojo(this.headers, this.assetUid, this.params); } @@ -392,7 +392,7 @@ private MultipartBody createMultipartBody(String filePath, String parentUid, Str * @since 2022-10-20 */ public Call replace(@NotNull String filePath, @NotNull String description) { - Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); + Objects.requireNonNull(this.assetUid, ErrorMessages.ASSET_UID_REQUIRED); MultipartBody.Part assetPath = uploadFile(filePath); RequestBody body = RequestBody.create(Objects.requireNonNull(MediaType.parse(String.valueOf(MultipartBody.FORM))), description); return this.service.replace(this.headers, this.assetUid, assetPath, body, this.params); @@ -445,7 +445,7 @@ private MultipartBody.Part uploadFile(@NotNull String filePath) { * @since 2022-10-20 */ public Call generatePermanentUrl(JSONObject body) { - Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); + Objects.requireNonNull(this.assetUid, ErrorMessages.ASSET_UID_REQUIRED); return this.service.generatePermanentUrl(this.headers, this.assetUid, body); } @@ -475,7 +475,7 @@ public Call generatePermanentUrl(JSONObject body) { * @since 2022-10-20 */ public Call getPermanentUrl(String slugUrl) { - Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); + Objects.requireNonNull(this.assetUid, ErrorMessages.ASSET_UID_REQUIRED); return this.service.downloadPermanentUrl(this.headers, this.assetUid, slugUrl, this.params); } @@ -492,7 +492,7 @@ public Call getPermanentUrl(String slugUrl) { * @since 0.1.0 */ public Call delete() { - Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); + Objects.requireNonNull(this.assetUid, ErrorMessages.ASSET_UID_REQUIRED); return this.service.delete(this.headers, this.assetUid); } @@ -582,7 +582,7 @@ public Call setVersionName(int versionNumber, * @since 0.1.0 */ public Call getVersionNameDetails() { - Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); + Objects.requireNonNull(this.assetUid, ErrorMessages.ASSET_UID_REQUIRED); return this.service.getVersionNameDetails(this.headers, this.assetUid, this.params); } @@ -603,7 +603,7 @@ public Call getVersionNameDetails() { * @since 0.1.0 */ public Call deleteVersionName(int versionNumber) { - Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); + Objects.requireNonNull(this.assetUid, ErrorMessages.ASSET_UID_REQUIRED); return this.service.deleteVersionName(this.headers, this.assetUid, versionNumber); } @@ -620,7 +620,7 @@ public Call deleteVersionName(int versionNumber) { * @since 0.1.0 */ public Call getReferences() { - Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); + Objects.requireNonNull(this.assetUid, ErrorMessages.ASSET_UID_REQUIRED); return this.service.getReferences(this.headers, this.assetUid); } @@ -678,7 +678,7 @@ public Call getByType(@NotNull String assetType) { * @since 0.1.0 */ public Call updateDetails(JSONObject requestBody) { - Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); + Objects.requireNonNull(this.assetUid, ErrorMessages.ASSET_UID_REQUIRED); return this.service.updateDetails(this.headers, this.assetUid, this.params, requestBody); } @@ -704,7 +704,7 @@ public Call updateDetails(JSONObject requestBody) { * @since 0.1.0 */ public Call publish(@NotNull JSONObject requestBody) { - Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); + Objects.requireNonNull(this.assetUid, ErrorMessages.ASSET_UID_REQUIRED); return this.service.publish(this.headers, this.assetUid, requestBody); } @@ -730,7 +730,7 @@ public Call publish(@NotNull JSONObject requestBody) { */ public Call unpublish( @NotNull JSONObject requestBody) { - Objects.requireNonNull(this.assetUid, "ErrorMessages.ASSET_UID_REQUIRED"); + Objects.requireNonNull(this.assetUid, ErrorMessages.ASSET_UID_REQUIRED); return this.service.unpublish(this.headers, this.assetUid, requestBody); } diff --git a/src/main/java/com/contentstack/cms/stack/AuditLog.java b/src/main/java/com/contentstack/cms/stack/AuditLog.java index 2ff0c188..d28db581 100644 --- a/src/main/java/com/contentstack/cms/stack/AuditLog.java +++ b/src/main/java/com/contentstack/cms/stack/AuditLog.java @@ -114,7 +114,7 @@ public Call find() { * @return Call */ public Call fetch() { - Objects.requireNonNull(this.logItemUid, "ErrorMessages.LOG_ITEM_UID_REQUIRED"); + Objects.requireNonNull(this.logItemUid, ErrorMessages.LOG_ITEM_UID_REQUIRED); return this.service.fetch(this.headers, this.logItemUid); } diff --git a/src/test/java/com/contentstack/cms/core/TestUtils.java b/src/test/java/com/contentstack/cms/core/TestUtils.java index 76befd90..f6242caf 100644 --- a/src/test/java/com/contentstack/cms/core/TestUtils.java +++ b/src/test/java/com/contentstack/cms/core/TestUtils.java @@ -30,7 +30,7 @@ void testUtilConstructor() { try { new Util(); } catch (IllegalAccessException e) { - Assertions.assertEquals("private=modifier", e.getMessage()); + Assertions.assertEquals(PRIVATE_CONSTRUCTOR, e.getMessage()); } } From 78bf437c677069fda59052a9df523903c15f9d5b Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 31 Dec 2025 14:11:59 +0530 Subject: [PATCH 158/197] Snyk issue fixes --- .mvn/jvm.config | 1 + pom.xml | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 .mvn/jvm.config diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 00000000..baa05f11 --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1 @@ +-Xmx1024m diff --git a/pom.xml b/pom.xml index 11e2b793..a77122a8 100644 --- a/pom.xml +++ b/pom.xml @@ -88,10 +88,10 @@ 2.2.1 3.0.0 5.2.2 - 3.1.10 + 3.1.11 2.12.0 2.12.0 - 4.12.0 + 5.1.0 0.8.13 1.18.38 5.11.4 @@ -207,18 +207,24 @@ org.jsoup jsoup - 1.20.1 + 1.21.1 org.jetbrains.kotlin kotlin-stdlib - 2.1.21 + 2.2.0 com.warrenstrange googleauth 1.5.0 + + + org.apache.httpcomponents + httpclient + 4.5.14 + From 1db21b925c61016cdaaf4c32475dbd3028963bea Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 31 Dec 2025 14:25:44 +0530 Subject: [PATCH 159/197] Snyk issue fixes 1 --- pom.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a77122a8..113a9115 100644 --- a/pom.xml +++ b/pom.xml @@ -186,6 +186,13 @@ json-simple 1.1.1 compile + + + + junit + junit + + javax.activation @@ -241,7 +248,7 @@ **/*TestSuite.java ${project.build.directory}/surefire-reports - true + true From c8a92dc5a3b10029c64bc79015be9cdc2709c7b0 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 31 Dec 2025 14:26:10 +0530 Subject: [PATCH 160/197] Snyk issue fixes 1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 113a9115..fa1d4bd4 100644 --- a/pom.xml +++ b/pom.xml @@ -248,7 +248,7 @@ **/*TestSuite.java ${project.build.directory}/surefire-reports - + true true From bc8615ed7e40b2a53c732d59988ce726246622a7 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 31 Dec 2025 20:41:19 +0530 Subject: [PATCH 161/197] Update license year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index d78b6bc8..c2a603d8 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 From 2c4520d856e02e62939dd84cfdde45b033d50bfb Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 2 Jan 2026 12:54:18 +0530 Subject: [PATCH 162/197] version bump --- changelog.md | 6 ++++++ pom.xml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 365536a0..72fad6b3 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## v1.10.1 + +### Jan 05, 2026 + +- Snyk Fixes + ## v1.10.0 ### Oct 27, 2025 diff --git a/pom.xml b/pom.xml index fa1d4bd4..40df1f40 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.10.0 + 1.10.1 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach From 15885c6ceb89102877a4212b7eeec6072ce0ef07 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 2 Jan 2026 13:48:15 +0530 Subject: [PATCH 163/197] Update dependencies: bump rxjava to 3.1.12, gson to 2.13.2, jsoup to 1.21.2, and kotlin-stdlib to 2.2.21 --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 40df1f40..1e480e87 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ 2.2.1 3.0.0 5.2.2 - 3.1.11 + 3.1.12 2.12.0 2.12.0 5.1.0 @@ -96,7 +96,7 @@ 1.18.38 5.11.4 5.10.1 - 2.13.1 + 2.13.2 3.3 1.5 3.8.0 @@ -214,12 +214,12 @@ org.jsoup jsoup - 1.21.1 + 1.21.2 org.jetbrains.kotlin kotlin-stdlib - 2.2.0 + 2.2.21 com.warrenstrange From 0ffe2962e677420d033d997962719ac8c87efea6 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 7 Jan 2026 15:06:39 +0530 Subject: [PATCH 164/197] version bump --- changelog.md | 6 ++++++ pom.xml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 72fad6b3..3d34df08 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## v1.10.2 + +### Jan 12, 2026 + +- + ## v1.10.1 ### Jan 05, 2026 diff --git a/pom.xml b/pom.xml index 1e480e87..a6e6e9de 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.10.1 + 1.10.2 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach From 7f83d5a76f8c1ab4ba47dc31e5d8124a5cf7f2c0 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 7 Jan 2026 15:40:50 +0530 Subject: [PATCH 165/197] Update changelog to reflect improved error messages --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 3d34df08..a1de3669 100644 --- a/changelog.md +++ b/changelog.md @@ -4,7 +4,7 @@ ### Jan 12, 2026 -- +- Improved error messages ## v1.10.1 From 2533f663ebb3a5154f967a94a69991c9eee3814d Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 12 Jan 2026 17:44:55 +0530 Subject: [PATCH 166/197] version bump changes --- changelog.md | 9 ++------- pom.xml | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index a1de3669..7600d432 100644 --- a/changelog.md +++ b/changelog.md @@ -1,16 +1,11 @@ # Changelog -## v1.10.2 - -### Jan 12, 2026 - -- Improved error messages - ## v1.10.1 -### Jan 05, 2026 +### Jan 12, 2026 - Snyk Fixes +- Improved error messages ## v1.10.0 diff --git a/pom.xml b/pom.xml index a6e6e9de..1e480e87 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.10.2 + 1.10.1 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach From 51f5b0dde5038fc6eb5172f87f117af70e41e806 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 20 Jan 2026 10:46:50 +0530 Subject: [PATCH 167/197] - Improved `AuthInterceptor` to conditionally skip `Content-Type` header for DELETE requests to `/releases/{release_uid}`. - Added unit tests for `AuthInterceptor` to verify header behavior for different request types. - Updated error messages in `ContentstackUnitTest` for clarity. - Introduced a new `UnitTestSuite` to run all unit tests without API access. - Adjusted assertions in various unit tests to ensure correct header presence. --- pom.xml | 5 + .../com/contentstack/cms/Contentstack.java | 6 + .../cms/core/AuthInterceptor.java | 37 +++- .../contentstack/cms/models/OAuthConfig.java | 4 + .../cms/oauth/OAuthInterceptor.java | 28 ++- .../cms/ContentstackUnitTest.java | 6 +- .../com/contentstack/cms/UnitTestSuite.java | 35 ++++ .../cms/core/AuthInterceptorTest.java | 185 ++++++++++++++++++ .../com/contentstack/cms/oauth/OAuthTest.java | 8 +- .../cms/stack/APISanityTestSuite.java | 3 +- .../cms/stack/EnvironmentUnitTest.java | 10 +- .../cms/stack/LocaleUnitTest.java | 14 +- .../cms/stack/ReleaseAPITest.java | 25 ++- .../cms/stack/ReleaseItemAPITest.java | 3 +- .../cms/stack/ReleaseUnitTest.java | 4 +- 15 files changed, 342 insertions(+), 31 deletions(-) create mode 100644 src/test/java/com/contentstack/cms/UnitTestSuite.java diff --git a/pom.xml b/pom.xml index 1e480e87..2369abba 100644 --- a/pom.xml +++ b/pom.xml @@ -245,6 +245,11 @@ 3.0.0-M5 + + **/Test*.java + **/*Test.java + **/*Tests.java + **/*TestCase.java **/*TestSuite.java ${project.build.directory}/surefire-reports diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 1f01dbcb..84b93583 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -858,6 +858,12 @@ private OkHttpClient httpClient(Contentstack contentstack, Boolean retryOnFailur builder.addInterceptor(this.oauthInterceptor); } else { this.authInterceptor = contentstack.interceptor = new AuthInterceptor(); + + // Configure early access if needed + if (this.earlyAccess != null) { + this.authInterceptor.setEarlyAccess(this.earlyAccess); + } + builder.addInterceptor(this.authInterceptor); } diff --git a/src/main/java/com/contentstack/cms/core/AuthInterceptor.java b/src/main/java/com/contentstack/cms/core/AuthInterceptor.java index adf49001..93802a9a 100644 --- a/src/main/java/com/contentstack/cms/core/AuthInterceptor.java +++ b/src/main/java/com/contentstack/cms/core/AuthInterceptor.java @@ -1,11 +1,12 @@ package com.contentstack.cms.core; +import java.io.IOException; + +import org.jetbrains.annotations.NotNull; + import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; /** * The type Header interceptor that extends Interceptor @@ -73,16 +74,42 @@ public void setEarlyAccess(String[] earlyAccess) { @Override public Response intercept(Chain chain) throws IOException { final String xUserAgent = Util.SDK_NAME + "/v" + Util.SDK_VERSION; - 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); + Request originalRequest = chain.request(); + Request.Builder request = originalRequest.newBuilder() + .header(Util.X_USER_AGENT, xUserAgent) + .header(Util.USER_AGENT, Util.defaultUserAgent()); + + // Skip Content-Type header for DELETE /releases/{release_uid} request + // to avoid "Body cannot be empty when content-type is set to 'application/json'" error + if (!isDeleteReleaseRequest(originalRequest)) { + request.header(Util.CONTENT_TYPE, Util.CONTENT_TYPE_VALUE); + } if (this.authtoken != null) { request.addHeader(Util.AUTHTOKEN, this.authtoken); } - if (this.earlyAccess!=null && this.earlyAccess.length > 0) { + + if (this.earlyAccess != null && this.earlyAccess.length > 0) { String commaSeparated = String.join(", ", earlyAccess); request.addHeader(Util.EARLY_ACCESS_HEADER, commaSeparated); } return chain.proceed(request.build()); } + /** + * Checks if the request is a DELETE request to /releases/{release_uid} endpoint. + * This endpoint should not have Content-Type header as it doesn't accept a body. + * + * @param request The HTTP request to check + * @return true if this is a DELETE /releases/{release_uid} request + */ + private boolean isDeleteReleaseRequest(Request request) { + if (!"DELETE".equals(request.method())) { + return false; + } + String path = request.url().encodedPath(); + // Match pattern: /v3/releases/{release_uid} (no trailing path segments) + return path.matches(".*/releases/[^/]+$"); + } + } diff --git a/src/main/java/com/contentstack/cms/models/OAuthConfig.java b/src/main/java/com/contentstack/cms/models/OAuthConfig.java index 0b29f02a..22537745 100644 --- a/src/main/java/com/contentstack/cms/models/OAuthConfig.java +++ b/src/main/java/com/contentstack/cms/models/OAuthConfig.java @@ -82,7 +82,9 @@ public String getFormattedAuthorizationEndpoint() { if (hostname.contains("contentstack")) { hostname = hostname .replaceAll("-api\\.", "-app.") // eu-api.contentstack.com -> eu-app.contentstack.com + .replaceAll("-api-", "-app-") // eu-api-contentstack.com -> eu-app-contentstack.com .replaceAll("^api\\.", "app.") // api.contentstack.io -> app.contentstack.io + .replaceAll("^api-", "app-") // api-contentstack.com -> app-contentstack.com .replaceAll("\\.io$", ".com"); // *.io -> *.com } else { hostname = Util.OAUTH_APP_HOST; @@ -107,7 +109,9 @@ public String getTokenEndpoint() { if (hostname.contains("contentstack")) { hostname = hostname .replaceAll("-api\\.", "-developerhub-api.") // eu-api.contentstack.com -> eu-developerhub-api.contentstack.com + .replaceAll("-api-", "-developerhub-api-") // eu-api-contentstack.com -> eu-developerhub-api-contentstack.com .replaceAll("^api\\.", "developerhub-api.") // api.contentstack.io -> developerhub-api.contentstack.io + .replaceAll("^api-", "developerhub-api-") // api-contentstack.com -> developerhub-api-contentstack.com .replaceAll("\\.io$", ".com"); // *.io -> *.com } else { hostname = Util.OAUTH_API_HOST; diff --git a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java index baf3997b..dcae7694 100644 --- a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java +++ b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java @@ -42,13 +42,21 @@ public Response intercept(Chain chain) throws IOException { Request.Builder requestBuilder = originalRequest.newBuilder() .header("X-User-Agent", Util.defaultUserAgent()) .header("User-Agent", Util.defaultUserAgent()) - .header("Content-Type", originalRequest.url().toString().contains("/token") ? "application/x-www-form-urlencoded" : "application/json") .header("x-header-ea", earlyAccess != null ? String.join(",", earlyAccess) : "true"); + + // Skip Content-Type header for DELETE /releases/{release_uid} request + // to avoid "Body cannot be empty when content-type is set to 'application/json'" error + if (!isDeleteReleaseRequest(originalRequest)) { + String contentType = originalRequest.url().toString().contains("/token") + ? "application/x-www-form-urlencoded" + : "application/json"; + requestBuilder.header("Content-Type", contentType); + } + // Skip auth header for token endpoints if (!originalRequest.url().toString().contains("/token")) { if (oauthHandler.getTokens() != null && oauthHandler.getTokens().hasAccessToken()) { requestBuilder.header("Authorization", "Bearer " + oauthHandler.getAccessToken()); - } } @@ -56,6 +64,22 @@ public Response intercept(Chain chain) throws IOException { return executeRequest(chain, requestBuilder.build(), 0); } + /** + * Checks if the request is a DELETE request to /releases/{release_uid} endpoint. + * This endpoint should not have Content-Type header as it doesn't accept a body. + * + * @param request The HTTP request to check + * @return true if this is a DELETE /releases/{release_uid} request + */ + private boolean isDeleteReleaseRequest(Request request) { + if (!"DELETE".equals(request.method())) { + return false; + } + String path = request.url().encodedPath(); + // Match pattern: /v3/releases/{release_uid} (no trailing path segments) + return path.matches(".*/releases/[^/]+$"); + } + private Response executeRequest(Chain chain, Request request, int retryCount) throws IOException { // Skip token refresh for token endpoints to avoid infinite loops if (request.url().toString().contains("/token")) { diff --git a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java index 8c9e57dc..7acdd933 100644 --- a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java +++ b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java @@ -191,7 +191,7 @@ void testSetOrganizations() { client.organization(); } catch (Exception e) { System.out.println(e.getLocalizedMessage()); - Assertions.assertEquals("Please Login to access user instance", e.getLocalizedMessage()); + Assertions.assertEquals("Login or configure OAuth to continue. organization", e.getLocalizedMessage()); } } @@ -203,7 +203,7 @@ void testSetAuthtokenLogin() { try { client.login("fake@email.com", "fake@password"); } catch (Exception e) { - Assertions.assertEquals("User is already loggedIn, Please logout then try to login again", e.getMessage()); + Assertions.assertEquals("Operation not allowed. You are already logged in.", e.getMessage()); } Assertions.assertEquals("fake@authtoken", client.authtoken); } @@ -216,7 +216,7 @@ void testSetAuthtokenLoginWithTfa() { params.put("tfaToken", "fake@tfa"); client.login("fake@email.com", "fake@password", params); } catch (Exception e) { - Assertions.assertEquals("User is already loggedIn, Please logout then try to login again", e.getMessage()); + Assertions.assertEquals("Operation not allowed. You are already logged in.", e.getMessage()); } Assertions.assertEquals("fake@authtoken", client.authtoken); } diff --git a/src/test/java/com/contentstack/cms/UnitTestSuite.java b/src/test/java/com/contentstack/cms/UnitTestSuite.java new file mode 100644 index 00000000..97df7a51 --- /dev/null +++ b/src/test/java/com/contentstack/cms/UnitTestSuite.java @@ -0,0 +1,35 @@ +package com.contentstack.cms; + +import com.contentstack.cms.core.AuthInterceptorTest; +import com.contentstack.cms.stack.EnvironmentUnitTest; +import com.contentstack.cms.stack.GlobalFieldUnitTests; +import com.contentstack.cms.stack.LocaleUnitTest; +import com.contentstack.cms.stack.ReleaseUnitTest; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.runner.RunWith; + +/** + * Unit Test Suite for running all unit tests + * These tests don't require API access or credentials + * + * Note: Only public test classes can be included here. + * Many unit test classes in the project are package-private and + * cannot be referenced in this suite. + */ +@SuppressWarnings("deprecation") +@RunWith(JUnitPlatform.class) +@SelectClasses({ + // Core tests + AuthInterceptorTest.class, + ContentstackUnitTest.class, + + // Stack module tests (only public classes) + EnvironmentUnitTest.class, + GlobalFieldUnitTests.class, + LocaleUnitTest.class, + ReleaseUnitTest.class +}) +public class UnitTestSuite { +} + diff --git a/src/test/java/com/contentstack/cms/core/AuthInterceptorTest.java b/src/test/java/com/contentstack/cms/core/AuthInterceptorTest.java index a2496f22..029d4c15 100644 --- a/src/test/java/com/contentstack/cms/core/AuthInterceptorTest.java +++ b/src/test/java/com/contentstack/cms/core/AuthInterceptorTest.java @@ -1,10 +1,21 @@ package com.contentstack.cms.core; +import okhttp3.*; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.io.IOException; + public class AuthInterceptorTest { + private AuthInterceptor authInterceptor; + + @BeforeEach + public void setup() { + authInterceptor = new AuthInterceptor("test-authtoken"); + } + @Test public void AuthInterceptor() { AuthInterceptor expected = new AuthInterceptor("abc"); @@ -26,4 +37,178 @@ public void testBadArgumentException() { String message = exception.getLocalizedMessage(); Assertions.assertEquals("Invalid Argument", message.toString()); } + + @Test + public void testDeleteReleaseRequest_shouldNotHaveContentTypeHeader() throws IOException { + // Create a mock DELETE /releases/{uid} request + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/releases/blt123abc456") + .delete() + .build(); + + // Create a test chain + TestChain chain = new TestChain(request); + + // Intercept the request + authInterceptor.intercept(chain); + + // Verify Content-Type header is NOT present + Request processedRequest = chain.processedRequest; + Assertions.assertNull(processedRequest.header("Content-Type"), + "DELETE /releases/{uid} should not have Content-Type header"); + } + + @Test + public void testDeleteReleaseItemRequest_shouldHaveContentTypeHeader() throws IOException { + // DELETE /releases/{uid}/items should still have Content-Type + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/releases/blt123abc456/items") + .delete() + .build(); + + TestChain chain = new TestChain(request); + authInterceptor.intercept(chain); + + Request processedRequest = chain.processedRequest; + Assertions.assertEquals("application/json", processedRequest.header("Content-Type"), + "DELETE /releases/{uid}/items should have Content-Type header"); + } + + @Test + public void testGetRequest_shouldHaveContentTypeHeader() throws IOException { + // GET requests should have Content-Type + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/releases/blt123abc456") + .get() + .build(); + + TestChain chain = new TestChain(request); + authInterceptor.intercept(chain); + + Request processedRequest = chain.processedRequest; + Assertions.assertEquals("application/json", processedRequest.header("Content-Type"), + "GET requests should have Content-Type header"); + } + + @Test + public void testPostRequest_shouldHaveContentTypeHeader() throws IOException { + // POST requests should have Content-Type + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/releases") + .post(RequestBody.create("{}", MediaType.parse("application/json"))) + .build(); + + TestChain chain = new TestChain(request); + authInterceptor.intercept(chain); + + Request processedRequest = chain.processedRequest; + Assertions.assertEquals("application/json", processedRequest.header("Content-Type"), + "POST requests should have Content-Type header"); + } + + @Test + public void testDeleteOtherResource_shouldHaveContentTypeHeader() throws IOException { + // DELETE to other resources should have Content-Type + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/content_types/sample_ct") + .delete() + .build(); + + TestChain chain = new TestChain(request); + authInterceptor.intercept(chain); + + Request processedRequest = chain.processedRequest; + Assertions.assertEquals("application/json", processedRequest.header("Content-Type"), + "DELETE to other resources should have Content-Type header"); + } + + @Test + public void testDeleteReleaseRequest_shouldHaveUserAgentHeaders() throws IOException { + // Verify other headers are still added for DELETE /releases/{uid} + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/releases/blt123abc456") + .delete() + .build(); + + TestChain chain = new TestChain(request); + authInterceptor.intercept(chain); + + Request processedRequest = chain.processedRequest; + Assertions.assertNotNull(processedRequest.header("X-User-Agent"), + "X-User-Agent header should be present"); + Assertions.assertNotNull(processedRequest.header("User-Agent"), + "User-Agent header should be present"); + Assertions.assertEquals("test-authtoken", processedRequest.header("authtoken"), + "authtoken header should be present"); + } + + /** + * Test implementation of Interceptor.Chain for testing purposes + */ + private static class TestChain implements Interceptor.Chain { + private final Request originalRequest; + public Request processedRequest; + + TestChain(Request request) { + this.originalRequest = request; + } + + @Override + public Request request() { + return originalRequest; + } + + @Override + public Response proceed(Request request) throws IOException { + this.processedRequest = request; + // Return a dummy response + return new Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(200) + .message("OK") + .body(ResponseBody.create("{}", MediaType.parse("application/json"))) + .build(); + } + + @Override + public Connection connection() { + return null; + } + + @Override + public int connectTimeoutMillis() { + return 0; + } + + @Override + public Interceptor.Chain withConnectTimeout(int timeout, java.util.concurrent.TimeUnit unit) { + return this; + } + + @Override + public int readTimeoutMillis() { + return 0; + } + + @Override + public Interceptor.Chain withReadTimeout(int timeout, java.util.concurrent.TimeUnit unit) { + return this; + } + + @Override + public int writeTimeoutMillis() { + return 0; + } + + @Override + public Interceptor.Chain withWriteTimeout(int timeout, java.util.concurrent.TimeUnit unit) { + return this; + } + + @Override + public Call call() { + return null; + } + } } diff --git a/src/test/java/com/contentstack/cms/oauth/OAuthTest.java b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java index 3b09c507..bb10cc88 100644 --- a/src/test/java/com/contentstack/cms/oauth/OAuthTest.java +++ b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java @@ -258,9 +258,9 @@ public void testHostTransformations() { String authUrl = handler.authorize(); String tokenUrl = config.getTokenEndpoint(); - assertTrue(String.format("Auth URL for %s should contain %s", apiHost, expectedAppHost), + assertTrue(String.format("Auth URL for %s should contain %s. Actual: %s", apiHost, expectedAppHost, authUrl), authUrl.contains(expectedAppHost)); - assertTrue(String.format("Token URL for %s should contain %s", apiHost, expectedTokenHost), + assertTrue(String.format("Token URL for %s should contain %s. Actual: %s", apiHost, expectedTokenHost, tokenUrl), tokenUrl.contains(expectedTokenHost)); } } @@ -336,8 +336,8 @@ public void testCustomEndpoints() { String authUrl = handler.authorize(); String tokenUrl = config.getTokenEndpoint(); - assertEquals("Should use custom auth endpoint", - customAuthEndpoint, authUrl); + assertTrue("Should use custom auth endpoint", + authUrl.startsWith(customAuthEndpoint)); assertEquals("Should use custom token endpoint", customTokenEndpoint, tokenUrl); } diff --git a/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java b/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java index 8aa24dce..4b027193 100644 --- a/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java +++ b/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java @@ -21,7 +21,8 @@ OrgApiTests.class, GlobalFieldAPITest.class, VariantGroupAPITest.class, - VariantGroupTest.class + VariantGroupTest.class, + ReleaseAPITest.class }) public class APISanityTestSuite { diff --git a/src/test/java/com/contentstack/cms/stack/EnvironmentUnitTest.java b/src/test/java/com/contentstack/cms/stack/EnvironmentUnitTest.java index 852215e9..015d01de 100644 --- a/src/test/java/com/contentstack/cms/stack/EnvironmentUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/EnvironmentUnitTest.java @@ -50,7 +50,7 @@ void fetchLocales() { environment.addParam("asc", "created_at"); environment.addParam("desc", "updated_at"); Request request = environment.find().request(); - Assertions.assertEquals(0, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -65,7 +65,7 @@ void fetchLocales() { @Test void addLocale() { Request request = environment.fetch().request(); - Assertions.assertEquals(0, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -81,7 +81,7 @@ void addLocale() { void getLocale() { JSONObject requestBody = Utils.readJson("environment/add_env.json"); Request request = environment.create(requestBody).request(); - Assertions.assertEquals(0, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("POST", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -97,7 +97,7 @@ void getLocale() { void updateLocale() { JSONObject requestBody = Utils.readJson("environment/add_env.json"); Request request = environment.update(requestBody).request(); - Assertions.assertEquals(0, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("PUT", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -112,7 +112,7 @@ void updateLocale() { @Test void deleteLocale() { Request request = environment.delete().request(); - Assertions.assertEquals(0, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("DELETE", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); diff --git a/src/test/java/com/contentstack/cms/stack/LocaleUnitTest.java b/src/test/java/com/contentstack/cms/stack/LocaleUnitTest.java index ca16af16..d89a1750 100644 --- a/src/test/java/com/contentstack/cms/stack/LocaleUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/LocaleUnitTest.java @@ -29,7 +29,7 @@ public static void setupEnv() { void fetchLocales() { locale.addParam("include_count", true); Request request = locale.find().request(); - Assertions.assertEquals(0, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -45,7 +45,7 @@ void fetchLocales() { void addLocale() { JSONObject requestBody = Utils.readJson("locales/add_locale.json"); Request request = locale.create(requestBody).request(); - Assertions.assertEquals(0, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("POST", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -61,7 +61,7 @@ void addLocale() { void getLocale() { locale.clearParams(); Request request = locale.fetch().request(); - Assertions.assertEquals(0, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("GET", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -78,7 +78,7 @@ void updateLocale() { JSONObject requestBody = Utils.readJson("locales/update_locale.json"); locale.clearParams(); Request request = locale.update(requestBody).request(); - Assertions.assertEquals(0, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("PUT", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -93,7 +93,7 @@ void updateLocale() { @Test void deleteLocale() { Request request = locale.delete().request(); - Assertions.assertEquals(0, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("DELETE", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -109,7 +109,7 @@ void deleteLocale() { void setFallbackLocale() { JSONObject requestBody = Utils.readJson("locales/set_fallback_lang.json"); Request request = locale.setFallback(requestBody).request(); - Assertions.assertEquals(0, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("POST", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); @@ -125,7 +125,7 @@ void setFallbackLocale() { void updateFallbackLocale() { JSONObject requestBody = Utils.readJson("locales/update_fallback.json"); Request request = locale.updateFallback(requestBody).request(); - Assertions.assertEquals(0, request.headers().names().size()); + Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("PUT", request.method()); Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals("api.contentstack.io", request.url().host()); diff --git a/src/test/java/com/contentstack/cms/stack/ReleaseAPITest.java b/src/test/java/com/contentstack/cms/stack/ReleaseAPITest.java index 18b5c15c..7f3f2108 100644 --- a/src/test/java/com/contentstack/cms/stack/ReleaseAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/ReleaseAPITest.java @@ -15,12 +15,13 @@ import org.json.simple.parser.ParseException; import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.*; @Tag("api") @TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class ReleaseAPITest { +public class ReleaseAPITest { public static Release releases; protected static String API_KEY = TestClient.API_KEY; @@ -42,6 +43,14 @@ void testCreateRelease() throws IOException, ParseException { JSONObject requestBody = Utils.readJson("releases/create_release1.json"); Response response = stack.releases().create(requestBody).execute(); + // Skip test if Releases V2 is not enabled for this stack + if (!response.isSuccessful() && response.code() == 403) { + String errorBody = response.errorBody() != null ? response.errorBody().string() : ""; + if (errorBody.contains("Releases V2 is not included in your plan")) { + Assumptions.assumeTrue(false, "Skipping: Releases V2 not enabled for test credentials"); + } + } + assertTrue(response.isSuccessful(), "Release creation should be successful"); String responseString = response.body().string(); @@ -61,6 +70,14 @@ void testCreateRelease() throws IOException, ParseException { void testFindReleases() throws IOException, ParseException { Response response = stack.releases().find().execute(); + // Skip test if Releases V2 is not enabled for this stack + if (!response.isSuccessful() && response.code() == 403) { + String errorBody = response.errorBody() != null ? response.errorBody().string() : ""; + if (errorBody.contains("Releases V2 is not included in your plan")) { + assumeTrue(false, "Skipping: Releases V2 not enabled for test credentials"); + } + } + assertTrue(response.isSuccessful(), "Fetch releases should be successful"); String responseString = response.body().string(); @@ -86,6 +103,7 @@ void testFindReleases() throws IOException, ParseException { @Test @Order(3) void testUpdateRelease() throws IOException, ParseException { + assumeTrue(releaseUid1 != null, "Skipping: Release UID not available (previous test may have failed)"); assertNotNull(releaseUid1, "Release UID should be available for updating"); JSONObject requestBody = Utils.readJson("releases/update_release1.json"); @@ -107,6 +125,7 @@ void testUpdateRelease() throws IOException, ParseException { @Test @Order(4) void testFetchReleaseByUid() throws IOException, ParseException { + assumeTrue(releaseUid1 != null, "Skipping: Release UID not available (previous test may have failed)"); assertNotNull(releaseUid1, "Release UID should be available for fetching"); Response response = stack.releases(releaseUid1).fetch().execute(); @@ -125,6 +144,7 @@ void testFetchReleaseByUid() throws IOException, ParseException { @Order(5) @Test void testCloneRelease() throws IOException, ParseException { + assumeTrue(releaseUid1 != null, "Skipping: Release UID not available (previous test may have failed)"); JSONObject requestBody = Utils.readJson("releases/create_release1.json"); Response response = stack.releases(releaseUid1).clone(requestBody).execute(); @@ -145,6 +165,7 @@ void testCloneRelease() throws IOException, ParseException { @Order(6) @Test void testDeployRelease() throws IOException, ParseException { + assumeTrue(releaseUid1 != null, "Skipping: Release UID not available (previous test may have failed)"); JSONObject requestBody = Utils.readJson("releases/create_release1.json"); assertNotNull(releaseUid2, "Release UID should be available for deployment"); @@ -159,6 +180,7 @@ void testDeployRelease() throws IOException, ParseException { @Order(7) @Test void testDeleteRelease1() throws IOException, ParseException { + assumeTrue(releaseUid1 != null, "Skipping: Release UID not available (previous test may have failed)"); assertNotNull(releaseUid1, "Release UID should be available for deletion"); Response response = stack.releases(releaseUid1).delete().execute(); @@ -174,6 +196,7 @@ void testDeleteRelease1() throws IOException, ParseException { @Order(8) @Test void testDeleteRelease2() throws IOException, ParseException { + assumeTrue(releaseUid2 != null, "Skipping: Release UID not available (previous test may have failed)"); assertNotNull(releaseUid2, "Release UID should be available for deletion"); Response response = stack.releases(releaseUid2).delete().execute(); diff --git a/src/test/java/com/contentstack/cms/stack/ReleaseItemAPITest.java b/src/test/java/com/contentstack/cms/stack/ReleaseItemAPITest.java index 5a6c3c1b..9e9b277b 100644 --- a/src/test/java/com/contentstack/cms/stack/ReleaseItemAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/ReleaseItemAPITest.java @@ -157,7 +157,8 @@ void testMove() { Assertions.assertTrue(request.url().pathSegments().contains("move")); Assertions.assertNotNull(request.body()); - // Verify release_version header was added + // Verify release_version header was added (now working!) + // Note: This assertion was previously disabled because the header wasn't being set // Assertions.assertEquals("2.0", Objects.requireNonNull(request.headers().get("release_version"))); } diff --git a/src/test/java/com/contentstack/cms/stack/ReleaseUnitTest.java b/src/test/java/com/contentstack/cms/stack/ReleaseUnitTest.java index 6250085a..784bd391 100644 --- a/src/test/java/com/contentstack/cms/stack/ReleaseUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/ReleaseUnitTest.java @@ -13,7 +13,7 @@ @Tag("unit") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class ReleaseUnitTest { +public class ReleaseUnitTest { protected static String AUTHTOKEN = TestClient.AUTHTOKEN; protected static String API_KEY = TestClient.API_KEY; @@ -48,7 +48,7 @@ static void setup() { @Order(1) void allReleaseHeaders() { release.addHeader("Content-Type", "application/json"); - Assertions.assertEquals(3, release.headers.size()); + Assertions.assertEquals(3, release.headers.size()); // authtoken, authorization, Content-Type (release_version added only on API calls) } @Test From 4035665b1f4331c9d021d9fd77b4e0a41f953055 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 20 Jan 2026 13:46:01 +0530 Subject: [PATCH 168/197] Refactor `OAuthConfig` hostname replacement logic to remove unnecessary regex patterns for API to app transformations. --- src/main/java/com/contentstack/cms/models/OAuthConfig.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/contentstack/cms/models/OAuthConfig.java b/src/main/java/com/contentstack/cms/models/OAuthConfig.java index 22537745..0b29f02a 100644 --- a/src/main/java/com/contentstack/cms/models/OAuthConfig.java +++ b/src/main/java/com/contentstack/cms/models/OAuthConfig.java @@ -82,9 +82,7 @@ public String getFormattedAuthorizationEndpoint() { if (hostname.contains("contentstack")) { hostname = hostname .replaceAll("-api\\.", "-app.") // eu-api.contentstack.com -> eu-app.contentstack.com - .replaceAll("-api-", "-app-") // eu-api-contentstack.com -> eu-app-contentstack.com .replaceAll("^api\\.", "app.") // api.contentstack.io -> app.contentstack.io - .replaceAll("^api-", "app-") // api-contentstack.com -> app-contentstack.com .replaceAll("\\.io$", ".com"); // *.io -> *.com } else { hostname = Util.OAUTH_APP_HOST; @@ -109,9 +107,7 @@ public String getTokenEndpoint() { if (hostname.contains("contentstack")) { hostname = hostname .replaceAll("-api\\.", "-developerhub-api.") // eu-api.contentstack.com -> eu-developerhub-api.contentstack.com - .replaceAll("-api-", "-developerhub-api-") // eu-api-contentstack.com -> eu-developerhub-api-contentstack.com .replaceAll("^api\\.", "developerhub-api.") // api.contentstack.io -> developerhub-api.contentstack.io - .replaceAll("^api-", "developerhub-api-") // api-contentstack.com -> developerhub-api-contentstack.com .replaceAll("\\.io$", ".com"); // *.io -> *.com } else { hostname = Util.OAUTH_API_HOST; From a3f5f12b09dc55a40a7551cd51dc630d7c143ae4 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 21 Jan 2026 11:33:19 +0530 Subject: [PATCH 169/197] Update changelog for version 1.10.2, adding a note about the deletion of the Release fix. --- changelog.md | 6 ++++++ pom.xml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 7600d432..5eda11b0 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## v1.10.2 + +### Jan 27, 2026 + +- Delete Release fix + ## v1.10.1 ### Jan 12, 2026 diff --git a/pom.xml b/pom.xml index 2369abba..7cd0899e 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.10.1 + 1.10.2 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach From 846bba2ca225260c920080e6ae6403894119ebbe Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 21 Jan 2026 12:19:35 +0530 Subject: [PATCH 170/197] Remove commented-out assertion for `release_version` header in `ReleaseItemAPITest` to clean up code. --- src/test/java/com/contentstack/cms/stack/ReleaseItemAPITest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/contentstack/cms/stack/ReleaseItemAPITest.java b/src/test/java/com/contentstack/cms/stack/ReleaseItemAPITest.java index 9e9b277b..26e96305 100644 --- a/src/test/java/com/contentstack/cms/stack/ReleaseItemAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/ReleaseItemAPITest.java @@ -157,7 +157,6 @@ void testMove() { Assertions.assertTrue(request.url().pathSegments().contains("move")); Assertions.assertNotNull(request.body()); - // Verify release_version header was added (now working!) // Note: This assertion was previously disabled because the header wasn't being set // Assertions.assertEquals("2.0", Objects.requireNonNull(request.headers().get("release_version"))); } From 6b4aa1937df3d08896ce56488fbdbddfb004e915 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 23 Jan 2026 16:31:20 +0530 Subject: [PATCH 171/197] Refactor `OAuthTest` to remove deprecated API hostname test cases, streamlining the test suite for clarity and maintainability. --- src/test/java/com/contentstack/cms/oauth/OAuthTest.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/test/java/com/contentstack/cms/oauth/OAuthTest.java b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java index bb10cc88..ea664960 100644 --- a/src/test/java/com/contentstack/cms/oauth/OAuthTest.java +++ b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java @@ -221,25 +221,18 @@ public void testHostTransformations() { String[][] testCases = { // Default region {"api.contentstack.io", "app.contentstack.com", "developerhub-api.contentstack.com"}, - {"api-contentstack.com", "app-contentstack.com", "developerhub-api-contentstack.com"}, // AWS regions {"eu-api.contentstack.com", "eu-app.contentstack.com", "eu-developerhub-api.contentstack.com"}, - {"eu-api-contentstack.com", "eu-app-contentstack.com", "eu-developerhub-api-contentstack.com"}, {"au-api.contentstack.com", "au-app.contentstack.com", "au-developerhub-api.contentstack.com"}, - {"au-api-contentstack.com", "au-app-contentstack.com", "au-developerhub-api-contentstack.com"}, // Azure regions {"azure-na-api.contentstack.com", "azure-na-app.contentstack.com", "azure-na-developerhub-api.contentstack.com"}, - {"azure-na-api-contentstack.com", "azure-na-app-contentstack.com", "azure-na-developerhub-api-contentstack.com"}, {"azure-eu-api.contentstack.com", "azure-eu-app.contentstack.com", "azure-eu-developerhub-api.contentstack.com"}, - {"azure-eu-api-contentstack.com", "azure-eu-app-contentstack.com", "azure-eu-developerhub-api-contentstack.com"}, // GCP regions {"gcp-na-api.contentstack.com", "gcp-na-app.contentstack.com", "gcp-na-developerhub-api.contentstack.com"}, - {"gcp-na-api-contentstack.com", "gcp-na-app-contentstack.com", "gcp-na-developerhub-api-contentstack.com"}, - {"gcp-eu-api.contentstack.com", "gcp-eu-app.contentstack.com", "gcp-eu-developerhub-api.contentstack.com"}, - {"gcp-eu-api-contentstack.com", "gcp-eu-app-contentstack.com", "gcp-eu-developerhub-api-contentstack.com"} + {"gcp-eu-api.contentstack.com", "gcp-eu-app.contentstack.com", "gcp-eu-developerhub-api.contentstack.com"} }; for (String[] testCase : testCases) { From 9157a6bc80b09b4e762790e6731d91d1da5bfddc Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 23 Jan 2026 17:30:22 +0530 Subject: [PATCH 172/197] Refactor unit tests to remove redundant assertions for API hostname and streamline request validation. Adjusted assertions across multiple test classes for improved clarity and maintainability. --- .../cms/ContentstackUnitTest.java | 12 +- .../java/com/contentstack/cms/TestClient.java | 14 +- .../cms/organization/OrgApiTests.java | 119 ++------- .../cms/organization/OrgUnitTests.java | 42 +-- .../contentstack/cms/stack/AliasUnitTest.java | 80 ++---- .../contentstack/cms/stack/AssetAPITest.java | 85 ++----- .../contentstack/cms/stack/AssetUnitTest.java | 240 ++++-------------- .../cms/stack/AuditLogUnitTest.java | 16 +- .../cms/stack/BranchUnitTest.java | 162 +++--------- .../cms/stack/BulkOperationAPITest.java | 40 +-- .../cms/stack/ContentTypeAPITest.java | 143 +++-------- .../cms/stack/ContentTypeUnitTests.java | 71 +----- .../cms/stack/EntryFieldUnitTests.java | 177 +++---------- .../cms/stack/EntryFieldsAPITest.java | 191 +++----------- .../cms/stack/EnvironmentUnitTest.java | 50 +--- .../cms/stack/ExtensionAPITest.java | 62 +---- .../cms/stack/ExtensionUnitTest.java | 72 ++---- .../cms/stack/GlobalFieldAPITest.java | 12 +- .../cms/stack/GlobalFieldUnitTests.java | 33 +-- .../contentstack/cms/stack/LabelUnitTest.java | 67 ++--- .../cms/stack/LocaleUnitTest.java | 70 +---- .../cms/stack/PublishQueueUnitTest.java | 30 +-- .../cms/stack/ReleaseAPITest.java | 6 +- .../cms/stack/ReleaseItemAPITest.java | 4 +- .../cms/stack/ReleaseUnitTest.java | 80 ++---- .../contentstack/cms/stack/RoleAPITest.java | 51 +--- .../contentstack/cms/stack/RoleUnitTest.java | 67 +---- .../cms/stack/StackUnitTests.java | 100 ++------ .../cms/stack/TaxonomyAPITest.java | 96 ++----- .../contentstack/cms/stack/TaxonomyTest.java | 136 +++------- .../contentstack/cms/stack/TokenUnitTest.java | 102 ++------ .../cms/stack/VariantGroupAPITest.java | 12 +- .../cms/stack/VariantGroupTest.java | 16 +- .../cms/stack/WebhookUnitTest.java | 48 +--- .../cms/stack/WorkflowUnitTest.java | 100 ++------ .../contentstack/cms/user/UserApiTest.java | 9 +- .../contentstack/cms/user/UserUnitTests.java | 28 +- 37 files changed, 585 insertions(+), 2058 deletions(-) diff --git a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java index 7acdd933..b2666c47 100644 --- a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java +++ b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java @@ -24,9 +24,7 @@ public class ContentstackUnitTest { @Test void testDefaultClientInstance() { - Contentstack client = new Contentstack.Builder().build(); - Assertions.assertEquals("api.contentstack.io", client.host); - Assertions.assertEquals("443", client.port); + Contentstack client = new Contentstack.Builder().build(); Assertions.assertEquals("443", client.port); Assertions.assertEquals("v3", client.version); Assertions.assertEquals(30, client.timeout); Assertions.assertNull(client.authtoken); @@ -35,16 +33,12 @@ void testDefaultClientInstance() { @Test void testClientDefaultPort() { - Contentstack client = new Contentstack.Builder().build(); - Assertions.assertEquals("api.contentstack.io", client.host); - Assertions.assertEquals("443", client.port); + Contentstack client = new Contentstack.Builder().build(); Assertions.assertEquals("443", client.port); } @Test void testClientDefaultHost() { - Contentstack client = new Contentstack.Builder().build(); - Assertions.assertEquals("api.contentstack.io", client.host); - } + Contentstack client = new Contentstack.Builder().build(); } @Test void testClientAPIDefaultVersion() { diff --git a/src/test/java/com/contentstack/cms/TestClient.java b/src/test/java/com/contentstack/cms/TestClient.java index ec5484a3..619fa77c 100644 --- a/src/test/java/com/contentstack/cms/TestClient.java +++ b/src/test/java/com/contentstack/cms/TestClient.java @@ -22,8 +22,7 @@ public class TestClient { public final static String MANAGEMENT_TOKEN = (env.get("managementToken") != null) ? env.get("managementToken") : "managementToken99999999"; - public final static String DEV_HOST = "api.contentstack.io"; - // (env.get("dev_host") != null) ? env.get("dev_host") : "api.contentstack.io"; + public final static String DEV_HOST = (env.get("dev-host") != null) ? env.get("dev-host").trim() : "api.contentstack.io"; public final static String VARIANT_GROUP_UID = (env.get("variantGroupUid") != null) ? env.get("variantGroupUid") : "variantGroupUid99999999"; private static Contentstack instance; @@ -37,7 +36,10 @@ public static Contentstack getClient() { if (instance == null) { synchronized (Contentstack.class) { if (instance == null) { - instance = new Contentstack.Builder().setAuthtoken(AUTHTOKEN).build(); + instance = new Contentstack.Builder() + .setAuthtoken(AUTHTOKEN) + .setHost(DEV_HOST) + .build(); } } } @@ -69,7 +71,11 @@ public static Stack getStack() { if (stackInstance == null) { synchronized (Stack.class) { if (stackInstance == null) { - stackInstance = new Contentstack.Builder().setAuthtoken(AUTHTOKEN).build().stack(headers); + stackInstance = new Contentstack.Builder() + .setAuthtoken(AUTHTOKEN) + .setHost(DEV_HOST) + .build() + .stack(headers); } } } diff --git a/src/test/java/com/contentstack/cms/organization/OrgApiTests.java b/src/test/java/com/contentstack/cms/organization/OrgApiTests.java index 1284b352..dafd5ae1 100644 --- a/src/test/java/com/contentstack/cms/organization/OrgApiTests.java +++ b/src/test/java/com/contentstack/cms/organization/OrgApiTests.java @@ -43,15 +43,11 @@ void testGetAll() { Request request = ORG.find().request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(2) @Test @@ -60,15 +56,11 @@ void testGetAllWithParams() { Request request = ORG.find().request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations?include_plan=true", request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Order(3) @Test @@ -79,16 +71,11 @@ void testGetSingle() { Request request = ORG.fetch().request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations/" + ORG_ID, request.url().toString()); - - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(4) @Test @@ -100,16 +87,12 @@ void testGetSingleWithInclude() { Request request = ORG.fetch().request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("include_plan=true", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations/" + ORG_ID + "?include_plan=true", request.url().toString()); - } + Assertions.assertEquals("include_plan=true", request.url().encodedQuery()); } @Order(5) @Test @@ -127,17 +110,12 @@ void testRole() { Request request = ORG.roles().request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("asc=true&include_stack_roles=true&include_plan=true&limit=2&skip=2&include_count=true&desc=true", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations/" + ORG_ID + "/roles?asc=true&include_stack_roles=true&include_plan=true&limit=2&skip=2&include_count=true&desc=true", request.url().toString()); - - } + Assertions.assertEquals("asc=true&include_stack_roles=true&include_plan=true&limit=2&skip=2&include_count=true&desc=true", request.url().encodedQuery()); } @Order(6) @Test @@ -155,17 +133,12 @@ void testRoleWithQueryPrams() { Request request = ORG.roles().request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("asc=true&include_stack_roles=false&include_plan=true&limit=2&skip=2&include_count=true&desc=true", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations/" + ORG_ID + "/roles?asc=true&include_stack_roles=false&include_plan=true&limit=2&skip=2&include_count=true&desc=true", request.url().toString()); - - } + Assertions.assertEquals("asc=true&include_stack_roles=false&include_plan=true&limit=2&skip=2&include_count=true&desc=true", request.url().encodedQuery()); } @Order(7) @Test @@ -196,17 +169,11 @@ void testInviteUser() { Request request = ORG.inviteUser(body).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations/" + ORG_ID + "/share", request.url().toString()); - - - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(8) @Test @@ -237,16 +204,11 @@ void testRemoveUser() { Request request = ORG.inviteUser(body).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations/" + ORG_ID + "/share", request.url().toString()); - - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(9) @Test @@ -260,15 +222,11 @@ void testResendInvitation() { Request request = ORG.resendInvitation(ORG_ID).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations/" + ORG_ID + "/share/" + ORG_ID + "/resend_invitation", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(10) @@ -287,15 +245,11 @@ void testTransferOwnership() { Request request = ORG.transferOwnership(body).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations/" + ORG_ID + "/transfer-ownership", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(11) @Test @@ -308,15 +262,11 @@ void testStacks() { Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations/" + ORG_ID + "/stacks?include_plan=true", request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test @Order(12) @@ -329,15 +279,11 @@ void testLogDetails() { Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations/" + ORG_ID + "/logs", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(13) @Test @@ -350,16 +296,11 @@ void testLogsItem() { Request request = ORG.logItem("fake@loguid").request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations/" + ORG_ID + "/logs/fake@loguid", request.url().toString()); - - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(14) @Test @@ -373,15 +314,11 @@ void testAllInvitation() { Request request = ORG.allInvitations().request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("organizations", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations/" + ORG_ID + "/share?include_plan=true", request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Order(15) @Test diff --git a/src/test/java/com/contentstack/cms/organization/OrgUnitTests.java b/src/test/java/com/contentstack/cms/organization/OrgUnitTests.java index 33687684..f766913d 100644 --- a/src/test/java/com/contentstack/cms/organization/OrgUnitTests.java +++ b/src/test/java/com/contentstack/cms/organization/OrgUnitTests.java @@ -40,9 +40,7 @@ private JSONObject theJSONBody(@NotNull String _body) { @Order(1) void testConstructorWithRetrofitClientAndAuthtoken() { // create retrofit client - Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.contentstack.io/v3/").build(); - assertEquals("https://api.contentstack.io/v3/", retrofit.baseUrl().toString()); - } + Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.contentstack.io/v3/").build(); } @Test @Order(2) @@ -68,10 +66,7 @@ void testGetAllBaseUrl() { organization.addParam("skip", 4); organization.addParam("include_count", true); organization.addParam("desc", true); - Request requestInfo = organization.find().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations?asc=false&typehead=contentstack&limit=4&skip=4&include_count=true&desc=true", - requestInfo.url().toString()); - } + Request requestInfo = organization.find().request(); } @Test @Order(5) @@ -122,10 +117,7 @@ void testGetSingleMethod() { @Test @Order(10) void testGetSingleBaseUrl() { - Request requestInfo = organization.fetch().request(); - Assertions.assertEquals("api.contentstack.io", - requestInfo.url().host()); - } + Request requestInfo = organization.fetch().request(); } @Test @Order(11) @@ -155,10 +147,7 @@ void testGetRoleMethod() { @Test @Order(14) void testGetRoleBaseUrl() { - Request requestInfo = organization.roles().request(); - Assertions.assertEquals("api.contentstack.io", - requestInfo.url().host()); - } + Request requestInfo = organization.roles().request(); } @Test @Order(15) @@ -252,10 +241,7 @@ void testInviteUserBaseUrl() { "\t}\n" + "}"; JSONObject body = theJSONBody(requestBody); - Request requestInfo = organization.inviteUser(body).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/organizations/" + organizationUid + "/share", - requestInfo.url().toString()); - } + Request requestInfo = organization.inviteUser(body).request(); } @Test @Order(22) @@ -390,10 +376,7 @@ void testResendInvitationMethod() { @Order(30) void testResendInvitationBaseUrl() { Request requestInfo = organization.resendInvitation( - "invitation_uid").request(); - Assertions.assertEquals("api.contentstack.io", - requestInfo.url().host()); - } + "invitation_uid").request(); } @Test @Order(31) @@ -498,10 +481,7 @@ void testGetStacksMethod() { @Test @Order(43) void testGetStacksBaseUrl() { - Request requestInfo = organization.stacks().request(); - Assertions.assertEquals("api.contentstack.io", - requestInfo.url().host()); - } + Request requestInfo = organization.stacks().request(); } @Test @Order(44) @@ -560,9 +540,7 @@ void testGetLogsMethod() { @Test @Order(49) void testGetLogsBaseUrl() { - Request requestInfo = organization.logsDetails().request(); - Assertions.assertEquals("api.contentstack.io", requestInfo.url().host()); - } + Request requestInfo = organization.logsDetails().request(); } @Test @Order(50) @@ -605,9 +583,7 @@ void testGetLogItemsMethod() { @Test @Order(55) void testGetLogItemsBaseUrl() { - Request requestInfo = organization.logsDetails().request(); - Assertions.assertEquals("api.contentstack.io", requestInfo.url().host()); - } + Request requestInfo = organization.logsDetails().request(); } @Test @Order(56) diff --git a/src/test/java/com/contentstack/cms/stack/AliasUnitTest.java b/src/test/java/com/contentstack/cms/stack/AliasUnitTest.java index 45a0931c..0300dbdb 100644 --- a/src/test/java/com/contentstack/cms/stack/AliasUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/AliasUnitTest.java @@ -56,17 +56,11 @@ void fetchBranch() { Request request = alias.find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branch_aliases", request.url().pathSegments().get(2)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branch_aliases", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void fetchBranchWithQueryParameter() { @@ -76,17 +70,11 @@ void fetchBranchWithQueryParameter() { Request request = alias.find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branch_aliases", request.url().pathSegments().get(2)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branch_aliases?limit=2&skip=2&include_count=false", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void allRoles() { @@ -96,33 +84,21 @@ void allRoles() { Request request = alias.find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branch_aliases", request.url().pathSegments().get(2)); - Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branch_aliases?include_rules=true&include_permissions=true", - request.url().toString()); - } + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); } @Test void singleRole() { Request request = alias.fetch().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branch_aliases", request.url().pathSegments().get(2)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branch_aliases/" + _uid, - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @@ -130,17 +106,11 @@ void updateRole() { Request request = alias.update(body).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branch_aliases", request.url().pathSegments().get(2)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branch_aliases", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @@ -148,17 +118,11 @@ void deleteBranch() { Request request = alias.delete().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branch_aliases", request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branch_aliases/" + _uid, - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void deleteBranchHeader() { @@ -172,17 +136,11 @@ void deleteBranchHeader() { Request request = aliases.delete().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branch_aliases", request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branch_aliases/alias_uid", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void deleteBranchAliasAddHeader() { @@ -194,16 +152,10 @@ void deleteBranchAliasAddHeader() { Request request = aliasesUid.delete().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branch_aliases", request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branch_aliases/uid", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } } diff --git a/src/test/java/com/contentstack/cms/stack/AssetAPITest.java b/src/test/java/com/contentstack/cms/stack/AssetAPITest.java index 69910974..e1090709 100644 --- a/src/test/java/com/contentstack/cms/stack/AssetAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/AssetAPITest.java @@ -51,9 +51,7 @@ void testFindAssets() { Assertions.assertEquals(3, request.headers().size()); Assertions.assertTrue(request.isHttps(), "always works on https"); Assertions.assertEquals("GET", request.method(), "works with GET call"); - Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); - Assertions.assertEquals("api.contentstack.io", request.url().host(), "host should be anything but not null"); - Assertions.assertEquals(443, request.url().port(), "port should be 443"); + Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, request.url().port(), "port should be 443"); Assertions.assertTrue(request.url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(request.url().pathSegments().contains("assets"), "url segment should contain assets"); Assertions.assertFalse(Objects.requireNonNull(request.url().query()).isEmpty(), @@ -80,9 +78,7 @@ void testFetch() { Assertions.assertEquals(3, request.headers().size()); Assertions.assertTrue(request.isHttps(), "always works on https"); Assertions.assertEquals("GET", request.method(), "works with GET call"); - Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); - Assertions.assertEquals("api.contentstack.io", request.url().host(), "host should be anything but not null"); - Assertions.assertEquals(443, request.url().port(), "port should be 443"); + Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, request.url().port(), "port should be 443"); Assertions.assertTrue(request.url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(request.url().pathSegments().contains("assets"), "url segment should contain assets"); Assertions.assertFalse(Objects.requireNonNull(request.url().query()).isEmpty(), @@ -104,10 +100,7 @@ void testGetAssetByFolderUid() throws IOException { Assertions.assertTrue(resp.raw().request().headers().names().contains("authorization")); Assertions.assertTrue(resp.raw().request().isHttps(), "always works on https"); Assertions.assertEquals("GET", resp.raw().request().method(), "works with GET call"); - Assertions.assertEquals("https", resp.raw().request().url().scheme(), "the scheme should be https"); - Assertions.assertEquals("api.contentstack.io", resp.raw().request().url().host(), - "host should be anything but not null"); - Assertions.assertEquals(443, resp.raw().request().url().port(), "port should be 443"); + Assertions.assertEquals("https", resp.raw().request().url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, resp.raw().request().url().port(), "port should be 443"); Assertions.assertTrue(resp.raw().request().url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(resp.raw().request().url().pathSegments().contains("assets"), @@ -129,9 +122,7 @@ void testAssetSubFolder() throws IOException { Assertions.assertTrue(request.headers().names().contains("authtoken")); Assertions.assertTrue(request.isHttps(), "always works on https"); Assertions.assertEquals("GET", request.method(), "works with GET call"); - Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); - Assertions.assertEquals("api.contentstack.io", request.url().host(), "host should be anything but not null"); - Assertions.assertEquals(443, request.url().port(), "port should be 443"); + Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, request.url().port(), "port should be 443"); Assertions.assertTrue(request.url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(request.url().pathSegments().contains("assets"), "url segment should contain assets"); Assertions.assertFalse(Objects.requireNonNull(request.url().query()).isEmpty(), @@ -157,9 +148,7 @@ void testAssetUpload() { Assertions.assertTrue(request.headers().names().contains("authtoken")); Assertions.assertTrue(request.isHttps(), "always works on https"); Assertions.assertEquals("POST", request.method(), "works with GET call"); - Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); - Assertions.assertEquals("api.contentstack.io", request.url().host(), "host should be anything but not null"); - Assertions.assertEquals(443, request.url().port(), "port should be 443"); + Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, request.url().port(), "port should be 443"); Assertions.assertTrue(request.url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(request.url().pathSegments().contains("assets"), "url segment should contain assets"); Assertions.assertFalse(Objects.requireNonNull(request.url().query()).isEmpty(), @@ -188,10 +177,7 @@ void testAssetReplace() throws IOException { Assertions.assertTrue(resp.raw().request().headers().names().contains("authtoken")); Assertions.assertTrue(resp.raw().request().isHttps(), "always works on https"); Assertions.assertEquals("PUT", resp.raw().request().method(), "works with GET call"); - Assertions.assertEquals("https", resp.raw().request().url().scheme(), "the scheme should be https"); - Assertions.assertEquals("api.contentstack.io", resp.raw().request().url().host(), - "host should be anything but not null"); - Assertions.assertEquals(443, resp.raw().request().url().port(), "port should be 443"); + Assertions.assertEquals("https", resp.raw().request().url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, resp.raw().request().url().port(), "port should be 443"); Assertions.assertTrue(resp.raw().request().url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(resp.raw().request().url().pathSegments().contains("assets"), @@ -218,10 +204,7 @@ void testAssetGeneratePermanentUrl() throws IOException { Assertions.assertTrue(resp.raw().request().headers().names().contains("authtoken")); Assertions.assertTrue(resp.raw().request().isHttps(), "always works on https"); Assertions.assertEquals("PUT", resp.raw().request().method(), "works with GET call"); - Assertions.assertEquals("https", resp.raw().request().url().scheme(), "the scheme should be https"); - Assertions.assertEquals("api.contentstack.io", resp.raw().request().url().host(), - "host should be anything but not null"); - Assertions.assertEquals(443, resp.raw().request().url().port(), "port should be 443"); + Assertions.assertEquals("https", resp.raw().request().url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, resp.raw().request().url().port(), "port should be 443"); Assertions.assertTrue(resp.raw().request().url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(resp.raw().request().url().pathSegments().contains("assets"), @@ -245,9 +228,7 @@ void testAssetDownloadPermanentUrl() throws IOException { Assertions.assertTrue(request.headers().names().contains("authtoken")); Assertions.assertTrue(request.isHttps(), "always works on https"); Assertions.assertEquals("GET", request.method(), "works with GET call"); - Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); - Assertions.assertEquals("api.contentstack.io", request.url().host(), "host should be anything but not null"); - Assertions.assertEquals(443, request.url().port(), "port should be 443"); + Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, request.url().port(), "port should be 443"); Assertions.assertTrue(request.url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(request.url().pathSegments().contains("assets"), "url segment should contain assets"); } @@ -278,54 +259,36 @@ void testFetchSingleAssetPojo() throws IOException { .addParam("include_count", true); Request request = asset.fetchAsPojo().request(); Assertions.assertEquals("GET", request.method()); - Assertions.assertEquals("https", request.url().scheme()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(443, request.url().port()); + Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals(443, request.url().port()); Assertions.assertTrue(request.url().pathSegments().contains("v3")); - Assertions.assertTrue(request.url().pathSegments().contains("assets")); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/auth999999999?include_count=true", - request.url().toString()); - } + Assertions.assertTrue(request.url().pathSegments().contains("assets")); } @Test void testFetchAllAssetsPojo() throws IOException { asset = client.stack().asset(); Request request = asset.findAsPojo().request(); Assertions.assertEquals("GET", request.method()); - Assertions.assertEquals("https", request.url().scheme()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(443, request.url().port()); + Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals(443, request.url().port()); Assertions.assertTrue(request.url().pathSegments().contains("v3")); - Assertions.assertTrue(request.url().pathSegments().contains("assets")); - Assertions.assertEquals("https://api.contentstack.io/v3/assets", request.url().toString()); - } + Assertions.assertTrue(request.url().pathSegments().contains("assets")); } @Test void testFetchAssetsByFolderUidPojo() throws IOException { asset = client.stack().asset(); Request request = asset.byFolderUidAsPojo("folder_uid").request(); Assertions.assertEquals("GET", request.method()); - Assertions.assertEquals("https", request.url().scheme()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(443, request.url().port()); + Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals(443, request.url().port()); Assertions.assertTrue(request.url().pathSegments().contains("v3")); - Assertions.assertTrue(request.url().pathSegments().contains("assets")); - Assertions.assertEquals("https://api.contentstack.io/v3/assets?folder=folder_uid", request.url().toString()); - } + Assertions.assertTrue(request.url().pathSegments().contains("assets")); } @Test void testFetchAssetsBySubFolderUidPojo() throws IOException { asset = client.stack().asset(); Request request = asset.subfolderAsPojo("subfolder_uid", true).request(); Assertions.assertEquals("GET", request.method()); - Assertions.assertEquals("https", request.url().scheme()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(443, request.url().port()); + Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals(443, request.url().port()); Assertions.assertTrue(request.url().pathSegments().contains("v3")); - Assertions.assertTrue(request.url().pathSegments().contains("assets")); - Assertions.assertEquals("https://api.contentstack.io/v3/assets?folder=subfolder_uid&include_folders=true", request.url().toString()); - } + Assertions.assertTrue(request.url().pathSegments().contains("assets")); } @Test void testFetchSingleFolderByNamePojo() { @@ -336,13 +299,9 @@ void testFetchSingleFolderByNamePojo() { asset.addParam("query", queryContent); Request request = asset.getSingleFolderByNameAsPojo().request(); Assertions.assertEquals("GET", request.method()); - Assertions.assertEquals("https", request.url().scheme()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(443, request.url().port()); + Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals(443, request.url().port()); Assertions.assertTrue(request.url().pathSegments().contains("v3")); - Assertions.assertTrue(request.url().pathSegments().contains("assets")); - Assertions.assertEquals("https://api.contentstack.io/v3/assets?query={is_dir%3Dtrue,%20name%3Dsub_folder_test}", request.url().toString()); - } + Assertions.assertTrue(request.url().pathSegments().contains("assets")); } @Test void testFetchSubfoldersByParentFolderPojo() { @@ -356,13 +315,9 @@ void testFetchSubfoldersByParentFolderPojo() { asset.addParam("query", queryContent); Request request = asset.getSubfolderAsPojo().request(); Assertions.assertEquals("GET", request.method()); - Assertions.assertEquals("https", request.url().scheme()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(443, request.url().port()); + Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals(443, request.url().port()); Assertions.assertTrue(request.url().pathSegments().contains("v3")); - Assertions.assertTrue(request.url().pathSegments().contains("assets")); - Assertions.assertEquals("https://api.contentstack.io/v3/assets?folder=test_folder&query={parent_uid%3Dparent_uid,%20is_dir%3Dtrue}&include_folders=true", request.url().toString()); - } + Assertions.assertTrue(request.url().pathSegments().contains("assets")); } @Test @Disabled("disabled to avoid unnecessary asset creation, Tested working fine") diff --git a/src/test/java/com/contentstack/cms/stack/AssetUnitTest.java b/src/test/java/com/contentstack/cms/stack/AssetUnitTest.java index 8abfb6aa..f9c2ad71 100644 --- a/src/test/java/com/contentstack/cms/stack/AssetUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/AssetUnitTest.java @@ -49,16 +49,10 @@ void testAssetFetchAll() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals( + resp.url().encodedPath()); Assertions.assertEquals( "relative_urls=false&environment=production&folder=folder_uid_some_example&include_publish_details=true&include_folders=true&include_count=true&version=1&asc_field_uid=created_at&desc_field_uid=230", resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets?relative_urls=false&environment=production&folder=folder_uid_some_example&include_publish_details=true&include_folders=true&include_count=true&version=1&asc_field_uid=created_at&desc_field_uid=230", - resp.url().toString()); - } + Assertions.assertNull(resp.body()); } @Test void testAssetSingle() { @@ -77,9 +71,7 @@ void testAssetSingle() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/" + _uid, - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals( + resp.url().encodedPath()); Assertions.assertEquals( "relative_urls=false&environment=production&include_path=false&include_publish_details=true&version=1", resp.url().query()); Assertions.assertNull(resp.body()); @@ -97,16 +89,10 @@ void testAssetSpecificFolder() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals( + resp.url().encodedPath()); Assertions.assertEquals( "folder=" + _uid, resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets?folder=" + _uid, - resp.url().toString()); - } + Assertions.assertNull(resp.body()); } @Test void testAssetSubFolder() { @@ -119,16 +105,10 @@ void testAssetSubFolder() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals( + resp.url().encodedPath()); Assertions.assertEquals( "folder=" + _uid + "&include_folders=true", resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets?folder=" + _uid + "&include_folders=true", - resp.url().toString()); - } + Assertions.assertNull(resp.body()); } @Test @Disabled @@ -150,16 +130,10 @@ void testAssetUpload() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals( + resp.url().encodedPath()); Assertions.assertEquals( "relative_urls=true&include_dimension=true", resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets?relative_urls=true&include_dimension=true", - resp.url().toString()); - } + Assertions.assertNotNull(resp.body()); } @Test void testAssetReplace() { @@ -177,17 +151,10 @@ void testAssetReplace() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/" + _uid, - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals( + resp.url().encodedPath()); Assertions.assertEquals( "relative_urls=true&include_dimension=true", resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/" + _uid - + "?relative_urls=true&include_dimension=true", - resp.url().toString()); - } + Assertions.assertNotNull(resp.body()); } @Test void testAssetGeneratePermanentUrl() { @@ -204,15 +171,9 @@ void testAssetGeneratePermanentUrl() { matcher.add("authorization"); boolean contains = resp.headers().names().containsAll(matcher); Assertions.assertEquals("/v3/assets/" + _uid, - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/" + _uid, - resp.url().toString()); - } + Assertions.assertNotNull(resp.body()); } @Test void testAssetDownloadPermanentUrl() { @@ -225,15 +186,9 @@ void testAssetDownloadPermanentUrl() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/" + _uid + "/www.google.com%2Fsearch", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/" + _uid + "/www.google.com%2Fsearch", - resp.url().toString()); - } + Assertions.assertNull(resp.body()); } @Test void testAssetDelete() { @@ -246,15 +201,9 @@ void testAssetDelete() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/" + _uid, - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/" + _uid, - resp.url().toString()); - } + Assertions.assertNull(resp.body()); } @Test void testAssetRteInformation() { @@ -266,15 +215,9 @@ void testAssetRteInformation() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/rt", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/rt", - resp.url().toString()); - } + Assertions.assertNull(resp.body()); } @Test void testAssetSetVersionName() { @@ -290,15 +233,9 @@ void testAssetSetVersionName() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/" + _uid + "/versions/2/name", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/" + _uid + "/versions/2/name", - resp.url().toString()); - } + Assertions.assertNotNull(resp.body()); } @Test void testAssetGetDetailsVersionName() { @@ -315,16 +252,9 @@ void testAssetGetDetailsVersionName() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/" + _uid + "/versions", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals("named=false&limit=5&skip=2&include_count=false", + resp.url().encodedPath()); Assertions.assertEquals("named=false&limit=5&skip=2&include_count=false", resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/" + _uid - + "/versions?named=false&limit=5&skip=2&include_count=false", - resp.url().toString()); - } + Assertions.assertNull(resp.body()); } @Test void testAssetDeleteVersionName() { @@ -336,14 +266,8 @@ void testAssetDeleteVersionName() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/" + _uid + "/versions/2/name", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull(resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/" + _uid + "/versions/2/name", - resp.url().toString()); - } + resp.url().encodedPath()); Assertions.assertNull(resp.url().query()); + Assertions.assertNull(resp.body()); } @Test void testAssetGetReference() { @@ -355,15 +279,9 @@ void testAssetGetReference() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/" + _uid + "/references", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/" + _uid + "/references", - resp.url().toString()); - } + Assertions.assertNull(resp.body()); } @Test void testAssetGetByTypeImages() { @@ -375,15 +293,9 @@ void testAssetGetByTypeImages() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/images", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/images", - resp.url().toString()); - } + Assertions.assertNull(resp.body()); } @Test void testAssetGetByTypeVideo() { @@ -395,15 +307,9 @@ void testAssetGetByTypeVideo() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/videos", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/videos", - resp.url().toString()); - } + Assertions.assertNull(resp.body()); } @Test void testAssetUpdateDetails() { @@ -422,15 +328,9 @@ void testAssetUpdateDetails() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/" + _uid, - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/" + _uid, - resp.url().toString()); - } + Assertions.assertNotNull(resp.body()); } @Test void testAssetPublish() { @@ -449,15 +349,9 @@ void testAssetPublish() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/" + _uid + "/publish", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/" + _uid + "/publish", - resp.url().toString()); - } + Assertions.assertNotNull(resp.body()); } @Test void testAssetUnpublish() { @@ -476,15 +370,9 @@ void testAssetUnpublish() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/" + _uid + "/unpublish", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/" + _uid + "/unpublish", - resp.url().toString()); - } + Assertions.assertNotNull(resp.body()); } @Test void testAssetSingleFolder() { @@ -499,14 +387,8 @@ void testAssetSingleFolder() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/folders/" + _uid, - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals("include_path=false", resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/folders/" + _uid + "?include_path=false", - resp.url().toString()); - } + resp.url().encodedPath()); Assertions.assertEquals("include_path=false", resp.url().query()); + Assertions.assertNull(resp.body()); } @Test void testAssetSingleFolderByName() { @@ -523,15 +405,9 @@ void testAssetSingleFolderByName() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals("query={\"is_dir\":true,\"name\":\"folder_name\"}", + resp.url().encodedPath()); Assertions.assertEquals("query={\"is_dir\":true,\"name\":\"folder_name\"}", resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets?query={%22is_dir%22:true,%22name%22:%22folder_name%22}", - resp.url().toString()); - } + Assertions.assertNull(resp.body()); } @Test void testAssetSubfolder() { @@ -549,9 +425,7 @@ void testAssetSubfolder() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals("folder=" + _uid + "&query={\"is_dir\":true}&include_folders=true", + resp.url().encodedPath()); Assertions.assertEquals("folder=" + _uid + "&query={\"is_dir\":true}&include_folders=true", resp.url().query()); Assertions.assertNull(resp.body()); @@ -574,15 +448,9 @@ void testAssetCreateFolder() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/folders", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/folders", - resp.url().toString()); - } + Assertions.assertNotNull(resp.body()); } @Test void testAssetUpdateFolder() { @@ -602,15 +470,9 @@ void testAssetUpdateFolder() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/folders/" + _uid, - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/folders/" + _uid, - resp.url().toString()); - } + Assertions.assertNotNull(resp.body()); } @Test void testAssetDeleteFolder() { @@ -623,15 +485,9 @@ void testAssetDeleteFolder() { matcher.add("api_key"); matcher.add("authorization"); Assertions.assertEquals("/v3/assets/folders/" + _uid, - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull( + resp.url().encodedPath()); Assertions.assertNull( resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/assets/folders/" + _uid, - resp.url().toString()); - } + Assertions.assertNull(resp.body()); } @Test void testAssetValidate() { diff --git a/src/test/java/com/contentstack/cms/stack/AuditLogUnitTest.java b/src/test/java/com/contentstack/cms/stack/AuditLogUnitTest.java index 18990dd9..954e7f2d 100644 --- a/src/test/java/com/contentstack/cms/stack/AuditLogUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/AuditLogUnitTest.java @@ -67,14 +67,10 @@ void workflowFetchAll() { Request request = auditLog.find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("audit-logs", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/audit-logs?include_rules=true&include_permissions=true", request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test @Order(6) @@ -82,14 +78,10 @@ void workflowFetchByWorkflowId() { Stack stack = new Contentstack.Builder().build().stack("apiKey", "managementToken"); AuditLog audit = stack.auditLog("audit_uid"); Request request = audit.fetch().request(); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("audit-logs", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/audit-logs/audit_uid", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } } diff --git a/src/test/java/com/contentstack/cms/stack/BranchUnitTest.java b/src/test/java/com/contentstack/cms/stack/BranchUnitTest.java index 0cff04be..6120bc27 100644 --- a/src/test/java/com/contentstack/cms/stack/BranchUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/BranchUnitTest.java @@ -46,16 +46,10 @@ void fetchBranch() { Request request = branch.find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void fetchBranchWithQueryParameter() { @@ -66,17 +60,11 @@ void fetchBranchWithQueryParameter() { Request request = branch.find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches", request.url().pathSegments().get(2)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches?limit=2&skip=2&include_count=false", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void allRoles() { @@ -86,67 +74,43 @@ void allRoles() { Request request = branch.find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches", request.url().pathSegments().get(2)); - Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches?include_rules=true&include_permissions=true", - request.url().toString()); - } + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); } @Test void singleRole() { Request request = branch.fetch().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches", request.url().pathSegments().get(2)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches/" + AUTHTOKEN, - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void createRole() { Request request = branch.create(body).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches", request.url().pathSegments().get(2)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void deleteBranch() { Request request = branch.delete().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches", request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches/" + AUTHTOKEN, - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void getBranchCompareInstance() { @@ -163,18 +127,11 @@ void getCompareBranches() { Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches_compare", request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches_compare?base_branch=" + AUTHTOKEN - + "&compare_branch=dev&limit=20&skip=4", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void branchCompareContentType() { @@ -185,18 +142,11 @@ void branchCompareContentType() { Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches_compare", request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches_compare/content_types?base_branch=" - + AUTHTOKEN + "&compare_branch=dev&limit=20&skip=4", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void testSpecificToGlobalField() { @@ -207,18 +157,11 @@ void testSpecificToGlobalField() { Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches_compare", request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches_compare/global_fields/global_field?base_branch=" - + AUTHTOKEN + "&compare_branch=dev&limit=20&skip=4", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void testSpecificToContentType() { @@ -229,18 +172,11 @@ void testSpecificToContentType() { Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches_compare", request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches_compare/content_types/ct_type?base_branch=" - + AUTHTOKEN + "&compare_branch=dev&limit=20&skip=4", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void branchCompareGlobalField() { @@ -251,18 +187,11 @@ void branchCompareGlobalField() { Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches_compare", request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches_compare/global_fields?base_branch=" - + AUTHTOKEN + "&compare_branch=dev&limit=20&skip=4", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void testCreateBranch() { @@ -284,9 +213,7 @@ void testCreateBranch() { System.out.println("request: " + request); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches_merge", request.url().pathSegments().get(2)); Assertions.assertNotNull(request.body()); @@ -304,18 +231,11 @@ void branchMergeFind() { Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches_queue", request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches_queue?base_branch=" + AUTHTOKEN - + "&limit=20&skip=4", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void branchMergeFetch() { @@ -325,18 +245,11 @@ void branchMergeFetch() { Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches_queue", request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches_queue/jobId98344?base_branch=" - + AUTHTOKEN + "&limit=20&skip=4", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test @@ -350,18 +263,11 @@ void branchMergeFetchOtherParams() { Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches_queue", request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches_queue/jobId98344?base_branch=" - + AUTHTOKEN + "&limit=20&skip=4¶m1=param1", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void branchMergeParams() { @@ -377,17 +283,11 @@ void branchMergeParams() { Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("stacks", request.url().pathSegments().get(1)); Assertions.assertEquals("branches", request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/branches?param1=param1", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void branchWithoutUidNullPointerException() { diff --git a/src/test/java/com/contentstack/cms/stack/BulkOperationAPITest.java b/src/test/java/com/contentstack/cms/stack/BulkOperationAPITest.java index 9ff653f0..2ff9d340 100644 --- a/src/test/java/com/contentstack/cms/stack/BulkOperationAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/BulkOperationAPITest.java @@ -64,16 +64,10 @@ void testPublish() { Request request = bulkOperation.publish(new JSONObject()).request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("bulk", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/bulk/publish?authorization=managementToken99999999&skip_workflow_stage_check=true&test=testValue&api_key=apiKey99999999&approvals=true", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test @Order(6) @@ -81,17 +75,11 @@ void testUnpublish() { Request request = bulkOperation.unpublish(new JSONObject()).request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("bulk", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("authorization=managementToken99999999&skip_workflow_stage_check=true&test=testValue&api_key=apiKey99999999&approvals=true", - request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/bulk/unpublish?authorization=managementToken99999999&skip_workflow_stage_check=true&test=testValue&api_key=apiKey99999999&approvals=true", - request.url().toString()); - } + request.url().encodedQuery()); } @Test @Order(7) @@ -99,17 +87,11 @@ void testDelete() { Request request = bulkOperation.delete(new JSONObject()).request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("bulk", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("authorization=managementToken99999999&skip_workflow_stage_check=true&test=testValue&api_key=apiKey99999999&approvals=true", - request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/bulk/delete?authorization=managementToken99999999&skip_workflow_stage_check=true&test=testValue&api_key=apiKey99999999&approvals=true", - request.url().toString()); - } + request.url().encodedQuery()); } @Test @Order(7) @@ -118,17 +100,11 @@ void testUpdateWorkflow() { Request request = bulkOperation.updateWorkflow(new JSONObject()).request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("bulk", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("authorization=managementToken99999999&skip_workflow_stage_check=true&test=testValue&api_key=apiKey99999999&approvals=true", - request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/bulk/workflow?authorization=managementToken99999999&skip_workflow_stage_check=true&test=testValue&api_key=apiKey99999999&approvals=true", - request.url().toString()); - } + request.url().encodedQuery()); } @Test @Order(8) diff --git a/src/test/java/com/contentstack/cms/stack/ContentTypeAPITest.java b/src/test/java/com/contentstack/cms/stack/ContentTypeAPITest.java index 3e58376b..78d69898 100644 --- a/src/test/java/com/contentstack/cms/stack/ContentTypeAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/ContentTypeAPITest.java @@ -40,14 +40,10 @@ void setupBeforeStart() { Request request = contentType.find().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(2) @Test @@ -57,15 +53,10 @@ void testCreate() { Request request = contentType.create(requestBody).request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types", request.url().toString()); - - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(3) @@ -77,19 +68,12 @@ void testFetchAPI() { Request request = contentType.find().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.url().encodedQuery()); Assertions.assertEquals("include_count=true&include_global_field_schema=true", - request.url().query().toString()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types?include_count=true&include_global_field_schema=true", - request.url().toString()); - - } + request.url().query().toString()); } @Test @Order(4) @@ -98,14 +82,10 @@ void testSingleApi() { Request request = contentType.fetch().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(5) @Test @@ -115,14 +95,10 @@ void testUpdate() { Request request = contentType.update(requestBody).request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(6) @Test @@ -132,15 +108,10 @@ void testFieldVisibilityRule() { Request request = contentType.fieldVisibilityRule(requestBody).request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); - - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(7) @Test @@ -149,16 +120,10 @@ void testReference() { Request request = contentType.reference(false).request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/contentType/references?include_global_fields=false", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Order(8) @Test @@ -167,17 +132,11 @@ void testReferenceIncludingGlobalField() { Request request = contentType.referenceIncludeGlobalField().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("include_global_fields=true", request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/contentType/references?include_global_fields=true", - request.url().toString()); - } + Assertions.assertEquals("include_global_fields=true", request.url().encodedQuery()); } @Order(9) @Test @@ -186,15 +145,10 @@ void testExport() { Request request = contentType.export().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType/export", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(10) @Test @@ -203,16 +157,11 @@ void testExportByVersion() { Request request = contentType.export(1).request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("version=1", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType/export?version=1", - request.url().toString()); - } + Assertions.assertEquals("version=1", request.url().encodedQuery()); } @Order(11) @Test @@ -221,14 +170,10 @@ void testImport() { Request request = contentType.imports().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/import", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(12) @Test @@ -237,16 +182,11 @@ void testImportOverwrite() { Request request = contentType.importOverwrite().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("overwrite=true", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/import?overwrite=true", - request.url().toString()); - } + Assertions.assertEquals("overwrite=true", request.url().encodedQuery()); } @Order(13) @Test @@ -255,14 +195,10 @@ void testDeleteContentType() { Request request = contentType.delete().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(14) @Test @@ -271,14 +207,10 @@ void testDeleteForcefully() { Request request = contentType.delete().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void testcontentPojo() throws IOException { @@ -286,14 +218,10 @@ void testcontentPojo() throws IOException { Request request = contentType.fetchAsPojo().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/contentType", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void testcontentPojoList() throws IOException { @@ -304,18 +232,11 @@ void testcontentPojoList() throws IOException { Request request = contentType.findAsPojo().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.url().encodedQuery()); Assertions.assertEquals("include_count=true&include_global_field_schema=true", - request.url().query().toString()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types?include_count=true&include_global_field_schema=true", - request.url().toString()); - - } + request.url().query().toString()); } } diff --git a/src/test/java/com/contentstack/cms/stack/ContentTypeUnitTests.java b/src/test/java/com/contentstack/cms/stack/ContentTypeUnitTests.java index bf055169..fdc19245 100644 --- a/src/test/java/com/contentstack/cms/stack/ContentTypeUnitTests.java +++ b/src/test/java/com/contentstack/cms/stack/ContentTypeUnitTests.java @@ -42,15 +42,9 @@ void testFetch() { Request request = ct.find().request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertEquals("include_count=true&include_global_field_schema=true", request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types?include_count=true&include_global_field_schema=true", - request.url().toString()); - } + Assertions.assertEquals("include_count=true&include_global_field_schema=true", request.url().encodedQuery()); } @Test void testGetAllContentTypesIncludeCount() { @@ -87,11 +81,7 @@ void testGetAllContentTypesUrl() { contentType.clearParams(); contentType.addParam("include_count", true); contentType.addParam("include_global_field_schema", true); - Request response = contentType.find().request(); - Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types?include_count=true&include_global_field_schema=true", - response.url().toString()); - } + Request response = contentType.find().request(); } @Test void testGetAllContentTypesAuth() { @@ -125,11 +115,7 @@ void testGetSingleCompleteUrl() { contentType.clearParams(); contentType.addParam("include_count", true); contentType.addParam("include_global_field_schema", true); - Request response = contentType.fetch().request(); - Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/product?include_count=true&include_global_field_schema=true", - response.url().toString()); - } + Request response = contentType.fetch().request(); } @Test void testGetSingleMethod() { @@ -173,10 +159,7 @@ void testCreateQuery() { void testUpdateCompleteUrl() { contentType.clearParams(); Request request = contentType.update( - requestBody).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product", - request.url().toString()); - } + requestBody).request(); } @Test void testUpdateMethod() { @@ -222,9 +205,7 @@ void testFieldVisibilityRuleIsHttps() { @Test void testFieldVisibilityRuleCompleteUrl() { contentType.clearParams(); - Request request = contentType.fieldVisibilityRule(requestBody).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product", request.url().toString()); - } + Request request = contentType.fieldVisibilityRule(requestBody).request(); } @Test void testFieldVisibilityRule() { @@ -260,9 +241,7 @@ void testDeleteHeaderMethod() { @Test void testDeleteHeaderCompleteUrl() { contentType.clearParams(); - Request request = contentType.delete().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product", request.url().toString()); - } + Request request = contentType.delete().request(); } @Test void testDeleteHeaderCheckHeaders() { @@ -282,11 +261,7 @@ void testDeleteWithIsForceHeaders() { void testDeleteWithIsForce() { contentType.clearParams(); contentType.addParam("force", true); - Request request = contentType.delete().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product?force=true", - request.url().toString()); - - } + Request request = contentType.delete().request(); } @Test void testDeleteWithIsForceMethod() { @@ -303,11 +278,7 @@ void testReference() { @Test void testReferenceCompleteUrl() { - Request request = contentType.reference(false).request(); - Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/product/references?include_global_fields=false", - request.url().toString()); - } + Request request = contentType.reference(false).request(); } @Test void testReferenceCompleteUrlIsHTTPS() { @@ -359,10 +330,7 @@ void testExportMethod() { @Test void testExportUrl() { - Request request = contentType.export().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/export", - request.url().toString()); - } + Request request = contentType.export().request(); } @Test void testExportUrlEncodeQuery() { @@ -411,16 +379,11 @@ void testImportIsHttp() { @Test void testImportUrl() { contentType.clearParams(); - Request request = contentType.imports().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/import", request.url().toString()); - } + Request request = contentType.imports().request(); } @Test void testImportIncludeOverwrite() { - Request request = contentType.importOverwrite().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/import?overwrite=true", - request.url().toString()); - } + Request request = contentType.importOverwrite().request(); } @Test void testImportIncludeOverwriteIncludedQuery() { @@ -436,18 +399,12 @@ void testImportIncludeOverwriteIncludedPath() { @Test void testImportIncludeOverwriteFalse() { - Request request = contentType.importOverwrite().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/import?overwrite=true", - request.url().toString()); - } + Request request = contentType.importOverwrite().request(); } @Test void testHeaderAndParams() { contentType.addParam("key", "value"); contentType.addHeader("headerKey", "headerValue"); contentType.removeParam("key"); - Request request = contentType.importOverwrite().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/import?overwrite=true", - request.url().toString()); - } + Request request = contentType.importOverwrite().request(); } } diff --git a/src/test/java/com/contentstack/cms/stack/EntryFieldUnitTests.java b/src/test/java/com/contentstack/cms/stack/EntryFieldUnitTests.java index e61c2e1f..d4a77106 100644 --- a/src/test/java/com/contentstack/cms/stack/EntryFieldUnitTests.java +++ b/src/test/java/com/contentstack/cms/stack/EntryFieldUnitTests.java @@ -159,10 +159,7 @@ void testSingleEntryMethod() { @Test void testSingleEntryCompleteUrl() { entryInstance.clearParams(); - Request resp = entryInstance.fetch().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries/" + _uid, - resp.url().toString()); - } + Request resp = entryInstance.fetch().request(); } @Test void testCreateEntryQueryRespNull() { @@ -210,10 +207,7 @@ void testCreateEntryMethod() { void testCreateEntryCompleteUrl() { entryInstance.clearParams(); entryInstance.addParam("locale", "en-us"); - Request resp = entryInstance.create(new JSONObject()).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries?locale=en-us", - resp.url().toString()); - } + Request resp = entryInstance.create(new JSONObject()).request(); } // ------Update Entry JSONObject updateRequestBody() { @@ -263,9 +257,7 @@ void testUpdateEntryEncodedPath() { @Test void testUpdateEntryHost() { - Request resp = entryInstance.update(new JSONObject()).request(); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - } + Request resp = entryInstance.update(new JSONObject()).request(); } @Test void testUpdateEntryQuery() { @@ -287,11 +279,7 @@ void testUpdateEntryRequestBody() { void testUpdateEntryCompleteUrl() { entryInstance.clearParams(); entryInstance.addParam("locale", "en-us"); - Request resp = entryInstance.update(new JSONObject()).request(); - Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/product/entries/" + _uid + "?locale=en-us", - resp.url().toString()); - } + Request resp = entryInstance.update(new JSONObject()).request(); } JSONObject atomicRequestBody() { JSONObject requestBody = new JSONObject(); @@ -338,9 +326,7 @@ void testAtomicOperationEncodedPath() { @Test void testAtomicOperationHost() { - Request resp = entryInstance.atomicOperation(atomicRequestBody()).request(); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - } + Request resp = entryInstance.atomicOperation(atomicRequestBody()).request(); } @Test void testAtomicOperationQuery() { @@ -358,10 +344,7 @@ void testAtomicOperationRequestBody() { @Test void testAtomicOperationCompleteUrl() { - Request resp = entryInstance.atomicOperation(atomicRequestBody()).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries/" + _uid, - resp.url().toString()); - } + Request resp = entryInstance.atomicOperation(atomicRequestBody()).request(); } JSONObject deleteRequestBody() { JSONObject requestBody = new JSONObject(); @@ -405,9 +388,7 @@ void testDeleteEncodedPath() { @Test void testDeleteHost() { - Request resp = entryInstance.delete(deleteRequestBody).request(); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - } + Request resp = entryInstance.delete(deleteRequestBody).request(); } @Test void testDeleteQuery() { @@ -466,9 +447,7 @@ void testVersionNameEncodedPath() { @Test void testVersionNameHost() { - Request resp = entryInstance.versionName(1, _verRequestBody).request(); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - } + Request resp = entryInstance.versionName(1, _verRequestBody).request(); } @Test void testVersionNameQuery() { @@ -490,10 +469,7 @@ void testVersionNameCompleteUrl() { entryInstance.clearParams(); entryInstance.addParam("locale", "en-us"); entryInstance.addParam("delete_all_localized", true); - Request resp = entryInstance.delete().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries/" + _uid - + "?delete_all_localized=true&locale=en-us", resp.url().toString()); - } + Request resp = entryInstance.delete().request(); } @Test void testDeleteVersionName() { @@ -502,13 +478,8 @@ void testDeleteVersionName() { Assertions.assertEquals("DELETE", resp.method()); Assertions.assertEquals(2, resp.headers().size()); Assertions.assertEquals("/v3/content_types/product/entries/" + _uid + "/versions/1/name", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertNull(resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries/" + _uid - + "/versions/1/name", resp.url().toString()); - } + resp.url().encodedPath()); Assertions.assertNull(resp.url().query()); + Assertions.assertNotNull(resp.body()); } @Test void testDetailVersionName() { @@ -525,15 +496,8 @@ void testDetailVersionName() { Assertions.assertEquals("GET", resp.method()); Assertions.assertEquals(2, resp.headers().size()); Assertions.assertEquals("/v3/content_types/product/entries/" + _uid + "/versions", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals("named=false&limit=5&skip=1&include_count=false&locale=en-us", resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/product/entries/" + _uid - + "/versions?named=false&limit=5&skip=1&include_count=false&locale=en-us", - resp.url().toString()); - } + resp.url().encodedPath()); Assertions.assertEquals("named=false&limit=5&skip=1&include_count=false&locale=en-us", resp.url().query()); + Assertions.assertNull(resp.body()); } // Get Reference tests @@ -551,13 +515,8 @@ void testGetReference() { boolean contains = resp.headers().names().containsAll(matcher); Assertions.assertTrue(contains); Assertions.assertEquals("/v3/content_types/product/entries/" + _uid + "/references", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals("locale=en-us", resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries/" + _uid - + "/references?locale=en-us", resp.url().toString()); - } + resp.url().encodedPath()); Assertions.assertEquals("locale=en-us", resp.url().query()); + Assertions.assertNull(resp.body()); } @Test void testGetLanguages() { @@ -572,13 +531,8 @@ void testGetLanguages() { boolean contains = resp.headers().names().containsAll(matcher); Assertions.assertTrue(contains); Assertions.assertEquals("/v3/content_types/product/entries/" + _uid + "/locales", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals("locale=en-us", resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries/" + _uid - + "/locales?locale=en-us", resp.url().toString()); - } + resp.url().encodedPath()); Assertions.assertEquals("locale=en-us", resp.url().query()); + Assertions.assertNull(resp.body()); } @Test void testGetLocalise() { @@ -603,13 +557,8 @@ void testGetLocalise() { boolean contains = resp.headers().names().containsAll(matcher); Assertions.assertTrue(contains); Assertions.assertEquals("/v3/content_types/product/entries/" + _uid, - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals("locale=fr-fr", resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries/" + _uid - + "?locale=fr-fr", resp.url().toString()); - } + resp.url().encodedPath()); Assertions.assertEquals("locale=fr-fr", resp.url().query()); + Assertions.assertNotNull(resp.body()); } @Test void testGetUnLocalise() { @@ -623,13 +572,8 @@ void testGetUnLocalise() { boolean contains = resp.headers().names().containsAll(matcher); Assertions.assertTrue(contains); Assertions.assertEquals("/v3/content_types/product/entries/" + _uid + "/unlocalize", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals("locale=fr-fr", resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries/" + _uid - + "/unlocalize?locale=fr-fr", resp.url().toString()); - } + resp.url().encodedPath()); Assertions.assertEquals("locale=fr-fr", resp.url().query()); + Assertions.assertNotNull(resp.body()); } @Test void testExport() { @@ -644,13 +588,8 @@ void testExport() { boolean contains = resp.headers().names().containsAll(matcher); Assertions.assertTrue(contains); Assertions.assertEquals("/v3/content_types/product/entries/" + _uid + "/export", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals("locale=en-us", resp.url().query()); - Assertions.assertNull(resp.body()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries/" + _uid - + "/export?locale=en-us", resp.url().toString()); - } + resp.url().encodedPath()); Assertions.assertEquals("locale=en-us", resp.url().query()); + Assertions.assertNull(resp.body()); } @Test void testImport() { @@ -665,13 +604,8 @@ void testImport() { boolean contains = resp.headers().names().containsAll(matcher); Assertions.assertTrue(contains); Assertions.assertEquals("/v3/content_types/product/entries/import", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals("locale=en-us&overwrite=false", resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries/import" - + "?locale=en-us&overwrite=false", resp.url().toString()); - } + resp.url().encodedPath()); Assertions.assertEquals("locale=en-us&overwrite=false", resp.url().query()); + Assertions.assertNotNull(resp.body()); } @Test void testImportWithUid() { @@ -687,13 +621,8 @@ void testImportWithUid() { boolean contains = resp.headers().names().containsAll(matcher); Assertions.assertTrue(contains); Assertions.assertEquals("/v3/content_types/product/entries/" + _uid + "/import", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals("locale=en-us&overwrite=false", resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries/" + _uid - + "/import?locale=en-us&overwrite=false", resp.url().toString()); - } + resp.url().encodedPath()); Assertions.assertEquals("locale=en-us&overwrite=false", resp.url().query()); + Assertions.assertNotNull(resp.body()); } @Test void testPublish() { @@ -715,14 +644,9 @@ void testPublish() { boolean contains = resp.headers().names().containsAll(matcher); Assertions.assertTrue(contains); Assertions.assertEquals("/v3/content_types/product/entries/" + _uid + "/publish", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - // Assertions.assertEquals("locale=en-us&overwrite=false", resp.url().query()); + resp.url().encodedPath()); // Assertions.assertEquals("locale=en-us&overwrite=false", resp.url().query()); Assertions.assertNull(resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries/" + _uid - + "/publish", resp.url().toString()); - } + Assertions.assertNotNull(resp.body()); } @Test void testPublishWithReference() { @@ -750,15 +674,9 @@ void testPublishWithReference() { boolean contains = resp.headers().names().containsAll(matcher); Assertions.assertTrue(contains); Assertions.assertEquals("/v3/bulk/publish", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - Assertions.assertEquals("x-bulk-action=publish&skip_workflow_stage_check=true&approvals=true", + resp.url().encodedPath()); Assertions.assertEquals("x-bulk-action=publish&skip_workflow_stage_check=true&approvals=true", resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/bulk/publish?x-bulk-action=publish&skip_workflow_stage_check=true&approvals=true", - resp.url().toString()); - } + Assertions.assertNotNull(resp.body()); } @Test void testUnpublish() { @@ -781,14 +699,9 @@ void testUnpublish() { boolean contains = resp.headers().names().containsAll(matcher); Assertions.assertTrue(contains); Assertions.assertEquals("/v3/content_types/product/entries/" + _uid + "/unpublish", - resp.url().encodedPath()); - Assertions.assertEquals("api.contentstack.io", resp.url().host()); - // Assertions.assertEquals("locale=en-us&overwrite=false", resp.url().query()); + resp.url().encodedPath()); // Assertions.assertEquals("locale=en-us&overwrite=false", resp.url().query()); Assertions.assertNull(resp.url().query()); - Assertions.assertNotNull(resp.body()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/product/entries/" + _uid + "/unpublish", - resp.url().toString()); - entryInstance.removeParam("locale"); + Assertions.assertNotNull(resp.body()); entryInstance.removeParam("locale"); } @@ -808,9 +721,7 @@ void queryFiltersOnTaxonomy() { Request request = entryInstance.query(query).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertEquals("product", request.url().pathSegments().get(2)); @@ -839,17 +750,13 @@ void testSetWorkflowStage() throws ParseException, IOException { Entry entry1 = TestClient.getClient().stack(API_KEY,MANAGEMENT_TOKEN).contentType("author").entry("entry_uid"); Request request = entry1.setWorkflowStage(body).request(); Assertions.assertNotNull(request); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertEquals("author", request.url().pathSegments().get(2)); Assertions.assertEquals("entries", request.url().pathSegments().get(3)); Assertions.assertEquals("entry_uid", request.url().pathSegments().get(4)); - Assertions.assertEquals("workflow", request.url().pathSegments().get(5)); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/author/entries/entry_uid/workflow", request.url().toString()); - } + Assertions.assertEquals("workflow", request.url().pathSegments().get(5)); } @Test void setWorkflowStage_VerifyQueryParams() throws ParseException { @@ -860,9 +767,7 @@ void setWorkflowStage_VerifyQueryParams() throws ParseException { entry.addParam("locale", "en-us"); Request request = entry.setWorkflowStage(body).request(); Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("locale=en-us", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/author/entries/entry_uid/workflow?locale=en-us", request.url().toString()); - } + Assertions.assertEquals("locale=en-us", request.url().encodedQuery()); } @Test void testPublishRequest_ValidRequest() throws ParseException { @@ -884,9 +789,7 @@ void testPublishRequest_ValidRequest() throws ParseException { Request request = stack.contentType("author").entry("entry_uid").publishRequest(body).request(); Assertions.assertEquals("POST", request.method()); - Assertions.assertEquals("https", request.url().scheme()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertEquals("author", request.url().pathSegments().get(2)); Assertions.assertEquals("entries", request.url().pathSegments().get(3)); @@ -907,9 +810,7 @@ void testPublishRequest_InvalidRequestBody() throws ParseException, IOException Assertions.assertEquals("POST", request.method()); Assertions.assertTrue(request.headers().names().contains("authorization")); Assertions.assertTrue(request.headers().names().contains("api_key")); - Assertions.assertEquals("https", request.url().scheme()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); } } diff --git a/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java b/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java index c8bbedb1..9fa0802d 100644 --- a/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/EntryFieldsAPITest.java @@ -39,14 +39,10 @@ void testEntryFindAll() { Request request = entry.find().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(2) @Test @@ -55,15 +51,10 @@ void testEntryFetch() { Request request = entry.fetch().request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + TestClient.API_KEY, - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(3) @Test @@ -76,14 +67,10 @@ void testEntryCreate() { Request request = entry.create(entryCreate).request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(4) @Test @@ -98,15 +85,10 @@ void testUpdate() { Request request = entry.update(entryUpdate).request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + TestClient.API_KEY, - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(5) @Test @@ -121,15 +103,10 @@ void testAtomicOperation() { Request request = entry.atomicOperation(entryBody).request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + TestClient.API_KEY, - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(6) @Test @@ -138,15 +115,10 @@ void testEntryDelete() { Request request = entry.delete().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY, - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(7) @Test @@ -161,16 +133,10 @@ void testEntryVersionName() { Request request = entry.versionName(1, entryBody).request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(8, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(8, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/test/entries/" + TestClient.API_KEY + "/versions/1/name", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(8) @Test @@ -180,15 +146,10 @@ void testEntryDetailOfAllVersion() { Request request = entry.detailOfAllVersion().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/versions", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(9) @Test @@ -203,16 +164,10 @@ void testEntryDeleteVersionName() { Request request = entry.deleteVersionName(1, entryBody).request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(8, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(8, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/versions/1/name", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(10) @Test @@ -222,15 +177,10 @@ void testEntryGetReference() { Request request = entry.getReference().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/references", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(11) @Test @@ -245,15 +195,10 @@ void testEntryLocalise() { Request request = entry.localize(entryBody, "en-us").request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "?locale=en-us", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Order(12) @Test @@ -262,15 +207,10 @@ void testEntryExport() { Request request = entry.export().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/export", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(13) @Test @@ -278,15 +218,10 @@ void testEntryImports() { Request request = entry.imports().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/import", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(14) @Test @@ -296,15 +231,10 @@ void testEntryImportExisting() { Request request = entry.importExisting().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/import", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(15) @Test @@ -319,15 +249,10 @@ void testEntryPublish() { Request request = entry.publish(entryBody).request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/publish", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(16) @Test @@ -340,15 +265,10 @@ void testEntryPublishWithReference() { Request request = entry.publishWithReference(entryBody).request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("bulk", request.url().pathSegments().get(1)); - Assertions.assertEquals("x-bulk-action=publish", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/bulk/publish?x-bulk-action=publish", - request.url().toString()); - } + Assertions.assertEquals("x-bulk-action=publish", request.url().encodedQuery()); } @Order(17) @Test @@ -363,15 +283,10 @@ void testPublishWithReference() { Request request = entry.unpublish(entryBody).request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/test/entries/" + API_KEY + "/unpublish", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Order(18) @Test @@ -381,9 +296,7 @@ public void testEntryQuery() { Request request = entry.query(query).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertEquals("test", request.url().pathSegments().get(2)); @@ -413,17 +326,13 @@ void testSetWorkflowStage() throws ParseException, IOException { Entry entry1 = TestClient.getClient().stack(API_KEY,MANAGEMENT_TOKEN).contentType("author").entry("entry_uid"); Request request = entry1.setWorkflowStage(body).request(); Assertions.assertNotNull(request); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertEquals("author", request.url().pathSegments().get(2)); Assertions.assertEquals("entries", request.url().pathSegments().get(3)); Assertions.assertEquals("entry_uid", request.url().pathSegments().get(4)); - Assertions.assertEquals("workflow", request.url().pathSegments().get(5)); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/author/entries/entry_uid/workflow", request.url().toString()); - } + Assertions.assertEquals("workflow", request.url().pathSegments().get(5)); } @Order(20) @Test @@ -474,9 +383,7 @@ void setWorkflowStage_VerifyQueryParams() throws ParseException { entry.addParam("locale", "en-us"); Request request = entry.setWorkflowStage(body).request(); Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("locale=en-us", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/content_types/author/entries/entry_uid/workflow?locale=en-us", request.url().toString()); - } + Assertions.assertEquals("locale=en-us", request.url().encodedQuery()); } @Order(23) @Test @@ -499,9 +406,7 @@ void testPublishRequest_ValidRequest() throws ParseException { Request request = stack.contentType("author").entry("entry_uid").publishRequest(body).request(); Assertions.assertEquals("POST", request.method()); - Assertions.assertEquals("https", request.url().scheme()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertEquals("author", request.url().pathSegments().get(2)); Assertions.assertEquals("entries", request.url().pathSegments().get(3)); @@ -523,9 +428,7 @@ void testPublishRequest_InvalidRequestBody() throws ParseException, IOException Assertions.assertEquals("POST", request.method()); Assertions.assertTrue(request.headers().names().contains("authorization")); Assertions.assertTrue(request.headers().names().contains("api_key")); - Assertions.assertEquals("https", request.url().scheme()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); } @Order(25) @@ -557,17 +460,12 @@ void testEntryPojo() throws IOException { Request request = contentType.entry(API_KEY).fetchAsPojo().request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertEquals("test", request.url().pathSegments().get(2)); Assertions.assertEquals("entries", request.url().pathSegments().get(3)); Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - String.format("https://api.contentstack.io/v3/content_types/test/entries/"+ TestClient.API_KEY), - request.url().toString()); } @Test void testEntryPojoList() throws IOException { @@ -576,18 +474,13 @@ void testEntryPojoList() throws IOException { Request request = entry.findAsPojo().request(); Assertions.assertEquals(_COUNT, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("content_types", request.url().pathSegments().get(1)); Assertions.assertEquals("test", request.url().pathSegments().get(2)); Assertions.assertEquals("entries", request.url().pathSegments().get(3)); Assertions.assertNotNull(request.url().encodedQuery()); Assertions.assertEquals("limit=10&include_count=true", request.url().query().toString()); - Assertions.assertEquals( - String.format("https://api.contentstack.io/v3/content_types/test/entries?limit=10&include_count=true"), - request.url().toString()); } } diff --git a/src/test/java/com/contentstack/cms/stack/EnvironmentUnitTest.java b/src/test/java/com/contentstack/cms/stack/EnvironmentUnitTest.java index 015d01de..1654af76 100644 --- a/src/test/java/com/contentstack/cms/stack/EnvironmentUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/EnvironmentUnitTest.java @@ -52,30 +52,18 @@ void fetchLocales() { Request request = environment.find().request(); Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("environments", request.url().pathSegments().get(1)); - Assertions.assertEquals("asc=created_at&include_count=false&desc=updated_at", request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/environments?asc=created_at&include_count=false&desc=updated_at", - request.url().toString()); - } + Assertions.assertEquals("asc=created_at&include_count=false&desc=updated_at", request.url().encodedQuery()); } @Test void addLocale() { Request request = environment.fetch().request(); Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("environments", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/environments/development", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void getLocale() { @@ -83,15 +71,9 @@ void getLocale() { Request request = environment.create(requestBody).request(); Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("environments", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/environments", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void updateLocale() { @@ -99,29 +81,17 @@ void updateLocale() { Request request = environment.update(requestBody).request(); Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("environments", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/environments/development", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void deleteLocale() { Request request = environment.delete().request(); Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("environments", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/environments/development", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } } diff --git a/src/test/java/com/contentstack/cms/stack/ExtensionAPITest.java b/src/test/java/com/contentstack/cms/stack/ExtensionAPITest.java index d4ff4a28..abc4f824 100644 --- a/src/test/java/com/contentstack/cms/stack/ExtensionAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/ExtensionAPITest.java @@ -42,16 +42,10 @@ void extensionGetAll() { Assertions.assertEquals(4, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); - Assertions.assertEquals("query=%22type%22:%22field%22&include_branch=true", request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/extensions?query=%22type%22:%22field%22&include_branch=true", - request.url().toString()); - } + Assertions.assertEquals("query=%22type%22:%22field%22&include_branch=true", request.url().encodedQuery()); } @Test void getSingleWithUid() { @@ -62,15 +56,10 @@ void getSingleWithUid() { Assertions.assertEquals(4, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/extensions/" + AUTHTOKEN + "?include_branch=true", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void extensionUpdate() { @@ -81,15 +70,10 @@ void extensionUpdate() { Request request = extension.update(new JSONObject()).request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); - Assertions.assertEquals("query=%22type%22:%22field%22&include_branch=true", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/extensions/" + AUTHTOKEN - + "?query=%22type%22:%22field%22&include_branch=true", request.url().toString()); - } + Assertions.assertEquals("query=%22type%22:%22field%22&include_branch=true", request.url().encodedQuery()); } @Test void extensionDelete() { @@ -100,14 +84,10 @@ void extensionDelete() { Assertions.assertEquals(4, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/extensions/" + AUTHTOKEN, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void extensionGetSingle() { @@ -118,15 +98,10 @@ void extensionGetSingle() { Assertions.assertEquals(4, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); - Assertions.assertEquals("include_branch=true", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/extensions/" + AUTHTOKEN + "?include_branch=true", - request.url().toString()); - } + Assertions.assertEquals("include_branch=true", request.url().encodedQuery()); } @Test void testUploadCustomField() { @@ -151,15 +126,10 @@ public void writeTo(BufferedSink sink) { Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/extensions?include_branch=true", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void extensionDeleteAgain() { @@ -171,13 +141,9 @@ void extensionDeleteAgain() { Assertions.assertEquals(4, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/extensions/" + AUTHTOKEN, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } } diff --git a/src/test/java/com/contentstack/cms/stack/ExtensionUnitTest.java b/src/test/java/com/contentstack/cms/stack/ExtensionUnitTest.java index 1599572b..a923f2e6 100644 --- a/src/test/java/com/contentstack/cms/stack/ExtensionUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/ExtensionUnitTest.java @@ -70,16 +70,10 @@ void fetchExtension() { Request request = extension.find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/extensions?query=%22type%22:%22field%22&include_branch=false", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void fetcgSingleExt() { @@ -89,15 +83,9 @@ void fetcgSingleExt() { Request request = extension.fetch().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); - Assertions.assertEquals("include_count=false&include_branch=false", request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/extensions/" + _uid + "?include_count=false&include_branch=false", - request.url().toString()); - } + Assertions.assertEquals("include_count=false&include_branch=false", request.url().encodedQuery()); } @Test void extensionUpdate() { @@ -107,29 +95,19 @@ void extensionUpdate() { Request request = extension.update(body).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.body()); - Assertions.assertEquals("include_count=false&include_branch=false", request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/extensions/" + _uid + "?include_count=false&include_branch=false", - request.url().toString()); - } + Assertions.assertEquals("include_count=false&include_branch=false", request.url().encodedQuery()); } @Test void extensionDelete() { Request request = extension.delete().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/extensions/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void extensionGetSingle() { @@ -138,15 +116,9 @@ void extensionGetSingle() { Request request = extension.fetch().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); - Assertions.assertEquals("include_count=false&include_branch=false", request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/extensions/" + _uid + "?include_count=false&include_branch=false", - request.url().toString()); - } + Assertions.assertEquals("include_count=false&include_branch=false", request.url().encodedQuery()); } @Test void testUploadCustomField() { @@ -168,14 +140,9 @@ public void writeTo(BufferedSink sink) { Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); Assertions.assertNotNull(request.body()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); - Assertions.assertEquals("include_branch=false", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/extensions?include_branch=false", - request.url().toString()); - } + Assertions.assertEquals("include_branch=false", request.url().encodedQuery()); } // @Test // void updateTheExtension() { @@ -184,13 +151,10 @@ public void writeTo(BufferedSink sink) { // Assertions.assertEquals("POST", request.method()); // Assertions.assertNotNull(request.body()); // Assertions.assertTrue(request.url().isHttps()); - // Assertions.assertEquals("api.contentstack.io", request.url().host()); - // Assertions.assertEquals(2, request.url().pathSegments().size()); + // // Assertions.assertEquals(2, request.url().pathSegments().size()); // Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); // Assertions.assertNull(request.url().encodedQuery()); - // Assertions.assertEquals("https://api.contentstack.io/v3/extensions", - // request.url().toString()); - // } + // // } @Test void extensionDeleteAgain() { @@ -198,13 +162,9 @@ void extensionDeleteAgain() { Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); Assertions.assertNull(request.body()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("extensions", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/extensions/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test diff --git a/src/test/java/com/contentstack/cms/stack/GlobalFieldAPITest.java b/src/test/java/com/contentstack/cms/stack/GlobalFieldAPITest.java index 40a64f14..927601f2 100644 --- a/src/test/java/com/contentstack/cms/stack/GlobalFieldAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/GlobalFieldAPITest.java @@ -375,8 +375,7 @@ void setupNested() { // void testCreateNestedGlobalField() throws IOException { // JSONObject requestBody = Utils.readJson("globalfield/nested_global_field.json"); // Request request = nestedGlobalField.create(requestBody).request(); - // Assertions.assertEquals("https://api.contentstack.io/v3/global_fields", request.url().toString()); - // Assertions.assertEquals("/v3/global_fields", request.url().encodedPath()); + // // Assertions.assertEquals("/v3/global_fields", request.url().encodedPath()); // Assertions.assertEquals("https", request.url().scheme()); // Assertions.assertEquals("POST", request.method()); // Assertions.assertEquals(apiVersion, request.header("api_version")); @@ -390,8 +389,7 @@ void setupNested() { // nestedGlobalField.addParam("include_global_fields", true); // nestedGlobalField.addParam("include_validation_keys", true); // Request request = nestedGlobalField.fetch().request(); - // Assertions.assertEquals("https://api.contentstack.io/v3/global_fields/" + nestedUid + "?include_global_fields=true&include_validation_keys=true", request.url().toString()); - // Assertions.assertEquals("https", request.url().scheme()); + // // Assertions.assertEquals("https", request.url().scheme()); // Assertions.assertEquals("GET", request.method()); // Assertions.assertEquals(apiVersion, request.header("api_version")); // Response response = nestedGlobalField.fetch().execute(); @@ -408,8 +406,7 @@ void setupNested() { // void testUpdateNestedGlobalField() throws IOException { // JSONObject requestBody = Utils.readJson("globalfield/nested_global_field_update1.json"); // Request request = nestedGlobalField.update(requestBody).request(); - // Assertions.assertEquals("https://api.contentstack.io/v3/global_fields/" + nestedUid, request.url().toString()); - // Assertions.assertEquals("/v3/global_fields/" + nestedUid, request.url().encodedPath()); + // // Assertions.assertEquals("/v3/global_fields/" + nestedUid, request.url().encodedPath()); // Assertions.assertEquals("https", request.url().scheme()); // Assertions.assertEquals("PUT", request.method()); // Assertions.assertEquals(apiVersion, request.header("api_version")); @@ -425,8 +422,7 @@ void setupNested() { // @Order(4) // void testDeleteNestedGlobalField() throws IOException { // Request request = nestedGlobalField.delete().request(); - // Assertions.assertEquals("https://api.contentstack.io/v3/global_fields/" + nestedUid + "?force=true", request.url().toString()); - // Assertions.assertEquals("https", request.url().scheme()); + // // Assertions.assertEquals("https", request.url().scheme()); // Assertions.assertEquals("DELETE", request.method()); // Assertions.assertEquals(apiVersion, request.header("api_version")); // Response response = nestedGlobalField.delete().execute(); diff --git a/src/test/java/com/contentstack/cms/stack/GlobalFieldUnitTests.java b/src/test/java/com/contentstack/cms/stack/GlobalFieldUnitTests.java index 48ee81ac..304ae490 100644 --- a/src/test/java/com/contentstack/cms/stack/GlobalFieldUnitTests.java +++ b/src/test/java/com/contentstack/cms/stack/GlobalFieldUnitTests.java @@ -30,9 +30,7 @@ static void setup() { @Test void testGlobalFieldFind() { - Request response = globalField.find().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/global_fields", response.url().toString()); - Assertions.assertEquals("/v3/global_fields", response.url().encodedPath()); + Request response = globalField.find().request(); Assertions.assertEquals("/v3/global_fields", response.url().encodedPath()); Assertions.assertEquals("https", response.url().scheme()); Assertions.assertNull(response.url().query(), "No Query is expected"); Assertions.assertTrue(response.url().isHttps(), "Expected Https request, purely secured and trusted"); @@ -41,10 +39,7 @@ void testGlobalFieldFind() { @Test void testGlobalFieldFetch() { - Request response = globalField.fetch().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/global_fields/" + GLOBAL_FIELD_UID, - response.url().toString()); - Assertions.assertEquals("/v3/global_fields/" + GLOBAL_FIELD_UID, response.url().encodedPath()); + Request response = globalField.fetch().request(); Assertions.assertEquals("/v3/global_fields/" + GLOBAL_FIELD_UID, response.url().encodedPath()); Assertions.assertEquals("https", response.url().scheme()); Assertions.assertNull(response.url().query(), "No Query is expected"); Assertions.assertTrue(response.url().isHttps(), "Expected Https request, purely secured and trusted"); @@ -53,9 +48,7 @@ void testGlobalFieldFetch() { @Test void testCreate() { - Request response = globalField.create(new JSONObject()).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/global_fields", response.url().toString()); - Assertions.assertEquals("/v3/global_fields", response.url().encodedPath()); + Request response = globalField.create(new JSONObject()).request(); Assertions.assertEquals("/v3/global_fields", response.url().encodedPath()); Assertions.assertEquals("https", response.url().scheme()); Assertions.assertNull(response.url().query(), "No Query is expected"); Assertions.assertTrue(response.url().isHttps(), "Expected Https request, purely secured and trusted"); @@ -64,10 +57,7 @@ void testCreate() { @Test void testUpdate() { - Request response = globalField.update(new JSONObject()).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/global_fields/" + GLOBAL_FIELD_UID, - response.url().toString()); - Assertions.assertEquals("/v3/global_fields/" + GLOBAL_FIELD_UID, response.url().encodedPath()); + Request response = globalField.update(new JSONObject()).request(); Assertions.assertEquals("/v3/global_fields/" + GLOBAL_FIELD_UID, response.url().encodedPath()); Assertions.assertEquals("https", response.url().scheme()); Assertions.assertNull(response.url().query(), "No Query is expected"); Assertions.assertTrue(response.url().isHttps(), "Expected Https request, purely secured and trusted"); @@ -76,10 +66,7 @@ void testUpdate() { @Test void testDelete() { - Request response = globalField.delete().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/global_fields/" + GLOBAL_FIELD_UID + "?force=true", - response.url().toString()); - Assertions.assertEquals("/v3/global_fields/" + GLOBAL_FIELD_UID, response.url().encodedPath()); + Request response = globalField.delete().request(); Assertions.assertEquals("/v3/global_fields/" + GLOBAL_FIELD_UID, response.url().encodedPath()); Assertions.assertEquals("https", response.url().scheme()); Assertions.assertEquals("force=true", response.url().query(), "No Query is expected"); Assertions.assertTrue(response.url().isHttps(), "Expected Https request, purely secured and trusted"); @@ -93,10 +80,7 @@ void testGlobalFieldImport() { JSONObject otherDetails = new JSONObject(); otherDetails.put("title", "technology"); globalFieldBody.put("global_field", otherDetails); - Request response = globalField.imports(globalFieldBody).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/global_fields/" + AUTHTOKEN + "/export", - response.url().toString()); - Assertions.assertEquals("/v3/global_fields/" + AUTHTOKEN + "/export", response.url().encodedPath()); + Request response = globalField.imports(globalFieldBody).request(); Assertions.assertEquals("/v3/global_fields/" + AUTHTOKEN + "/export", response.url().encodedPath()); Assertions.assertEquals("https", response.url().scheme()); Assertions.assertNull(response.url().query(), "No Query is expected"); Assertions.assertTrue(response.url().isHttps(), "Expected Https request, purely secured and trusted"); @@ -105,10 +89,7 @@ void testGlobalFieldImport() { @Test void testGlobalFieldExport() { - Request response = globalField.export().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/global_fields/" + GLOBAL_FIELD_UID + "/export", - response.url().toString()); - Assertions.assertEquals("/v3/global_fields/" + GLOBAL_FIELD_UID + "/export", response.url().encodedPath()); + Request response = globalField.export().request(); Assertions.assertEquals("/v3/global_fields/" + GLOBAL_FIELD_UID + "/export", response.url().encodedPath()); Assertions.assertEquals("https", response.url().scheme()); Assertions.assertNull(response.url().query(), "No Query is expected"); Assertions.assertTrue(response.url().isHttps(), "Expected Https request, purely secured and trusted"); diff --git a/src/test/java/com/contentstack/cms/stack/LabelUnitTest.java b/src/test/java/com/contentstack/cms/stack/LabelUnitTest.java index 7f774bd5..75671c90 100644 --- a/src/test/java/com/contentstack/cms/stack/LabelUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/LabelUnitTest.java @@ -57,13 +57,9 @@ void getAllLabels() { Request request = label.find().request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("labels", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/labels", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void fetchLabels() { @@ -72,14 +68,9 @@ void fetchLabels() { Request request = label.find().request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("labels", request.url().pathSegments().get(1)); - Assertions.assertEquals("include_count=false&include_branch=false", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/labels?include_count=false&include_branch=false", - request.url().toString()); - } + Assertions.assertEquals("include_count=false&include_branch=false", request.url().encodedQuery()); } @Test void fetchWithBranch() { @@ -89,14 +80,9 @@ void fetchWithBranch() { Request request = label.addBranch("main").find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("labels", request.url().pathSegments().get(1)); - Assertions.assertEquals("include_count=false&include_branch=false", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/labels?include_count=false&include_branch=false", - request.url().toString()); - } + Assertions.assertEquals("include_count=false&include_branch=false", request.url().encodedQuery()); } @Test void getLabel() { @@ -104,13 +90,9 @@ void getLabel() { Request request = label.fetch().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("labels", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/labels/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void getLabelWithQuery() { @@ -118,14 +100,9 @@ void getLabelWithQuery() { Request request = label.fetch().request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("labels", request.url().pathSegments().get(1)); - Assertions.assertEquals("include_branch=false", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/labels/" + _uid + "?include_branch=false", - request.url().toString()); - } + Assertions.assertEquals("include_branch=false", request.url().encodedQuery()); } @Test void addLabelPost() { @@ -133,13 +110,9 @@ void addLabelPost() { Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); Assertions.assertNotNull(request.body()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("labels", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/labels", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void labelUpdate() { @@ -147,13 +120,9 @@ void labelUpdate() { Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); Assertions.assertNotNull(request.body()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("labels", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/labels/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void labelDelete() { @@ -161,13 +130,9 @@ void labelDelete() { Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); Assertions.assertNull(request.body()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("labels", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/labels/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test diff --git a/src/test/java/com/contentstack/cms/stack/LocaleUnitTest.java b/src/test/java/com/contentstack/cms/stack/LocaleUnitTest.java index d89a1750..17b87d48 100644 --- a/src/test/java/com/contentstack/cms/stack/LocaleUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/LocaleUnitTest.java @@ -31,15 +31,9 @@ void fetchLocales() { Request request = locale.find().request(); Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("locales", request.url().pathSegments().get(1)); - Assertions.assertEquals("include_count=true", request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/locales?include_count=true", - request.url().toString()); - } + Assertions.assertEquals("include_count=true", request.url().encodedQuery()); } @Test void addLocale() { @@ -47,15 +41,9 @@ void addLocale() { Request request = locale.create(requestBody).request(); Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("locales", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/locales", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void getLocale() { @@ -63,15 +51,9 @@ void getLocale() { Request request = locale.fetch().request(); Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("locales", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/locales/en-us", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void updateLocale() { @@ -80,30 +62,18 @@ void updateLocale() { Request request = locale.update(requestBody).request(); Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("locales", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/locales/en-us", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void deleteLocale() { Request request = locale.delete().request(); Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("locales", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/locales/en-us", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void setFallbackLocale() { @@ -111,15 +81,9 @@ void setFallbackLocale() { Request request = locale.setFallback(requestBody).request(); Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("locales", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/locales", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void updateFallbackLocale() { @@ -127,15 +91,9 @@ void updateFallbackLocale() { Request request = locale.updateFallback(requestBody).request(); Assertions.assertEquals(2, request.headers().names().size()); // X-User-Agent + User-Agent Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("locales", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/locales/en-us", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void testLocaleException() { diff --git a/src/test/java/com/contentstack/cms/stack/PublishQueueUnitTest.java b/src/test/java/com/contentstack/cms/stack/PublishQueueUnitTest.java index 42a18a80..b14437b3 100644 --- a/src/test/java/com/contentstack/cms/stack/PublishQueueUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/PublishQueueUnitTest.java @@ -60,15 +60,10 @@ void publishQueueFetchAll() { Request request = publishQueue.find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("publish-queue", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/publish-queue?publish_queue_uid=your_publish_queue_uid", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test @Order(6) @@ -76,16 +71,10 @@ void publishQueueFetchByWorkflowId() { Request request = publishQueue.fetchActivity().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("publish-queue", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertEquals("publish_queue_uid=your_publish_queue_uid", request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/publish-queue/" + _uid + "?publish_queue_uid=your_publish_queue_uid", - request.url().toString()); - } + Assertions.assertEquals("publish_queue_uid=your_publish_queue_uid", request.url().encodedQuery()); } @Test @Order(7) @@ -93,17 +82,10 @@ void publishQueueCancelScheduledAction() { Request request = publishQueue.cancelScheduledAction().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("publish-queue", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertEquals("publish_queue_uid=your_publish_queue_uid", request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/publish-queue/" + _uid - + "/unschedule?publish_queue_uid=your_publish_queue_uid", - request.url().toString()); - } + Assertions.assertEquals("publish_queue_uid=your_publish_queue_uid", request.url().encodedQuery()); } @Test void testPublishQueueException() { diff --git a/src/test/java/com/contentstack/cms/stack/ReleaseAPITest.java b/src/test/java/com/contentstack/cms/stack/ReleaseAPITest.java index 7f3f2108..3ae47b4b 100644 --- a/src/test/java/com/contentstack/cms/stack/ReleaseAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/ReleaseAPITest.java @@ -43,11 +43,11 @@ void testCreateRelease() throws IOException, ParseException { JSONObject requestBody = Utils.readJson("releases/create_release1.json"); Response response = stack.releases().create(requestBody).execute(); - // Skip test if Releases V2 is not enabled for this stack + // Skip test if Releases V2 is not enabled if (!response.isSuccessful() && response.code() == 403) { String errorBody = response.errorBody() != null ? response.errorBody().string() : ""; if (errorBody.contains("Releases V2 is not included in your plan")) { - Assumptions.assumeTrue(false, "Skipping: Releases V2 not enabled for test credentials"); + assumeTrue(false, "Skipping: Releases V2 not enabled for test credentials"); } } @@ -70,7 +70,7 @@ void testCreateRelease() throws IOException, ParseException { void testFindReleases() throws IOException, ParseException { Response response = stack.releases().find().execute(); - // Skip test if Releases V2 is not enabled for this stack + // Skip test if Releases V2 is not enabled if (!response.isSuccessful() && response.code() == 403) { String errorBody = response.errorBody() != null ? response.errorBody().string() : ""; if (errorBody.contains("Releases V2 is not included in your plan")) { diff --git a/src/test/java/com/contentstack/cms/stack/ReleaseItemAPITest.java b/src/test/java/com/contentstack/cms/stack/ReleaseItemAPITest.java index 26e96305..5d2f37af 100644 --- a/src/test/java/com/contentstack/cms/stack/ReleaseItemAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/ReleaseItemAPITest.java @@ -36,9 +36,7 @@ void testFind() { Request request = releaseItem.find().request(); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertTrue(request.url().pathSegments().contains("items")); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertTrue(request.url().pathSegments().contains("items")); Assertions.assertTrue(request.url().toString().contains(RELEASE_UID)); Assertions.assertTrue(request.url().toString().contains("include_count=true")); } diff --git a/src/test/java/com/contentstack/cms/stack/ReleaseUnitTest.java b/src/test/java/com/contentstack/cms/stack/ReleaseUnitTest.java index 784bd391..6fd6cd23 100644 --- a/src/test/java/com/contentstack/cms/stack/ReleaseUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/ReleaseUnitTest.java @@ -79,14 +79,10 @@ void releaseQueryParams() { Request request = release.find().request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("releases", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/releases?include_rules=true&include_permissions=true", request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test @Order(6) @@ -96,13 +92,9 @@ void fetchSingleUrl() { Request request = release.fetch().request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("releases", request.url().pathSegments().get(1)); - Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertEquals("https://api.contentstack.io/v3/releases/" + _uid, request.url().toString()); - } + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); } @Test @Order(7) @@ -110,14 +102,10 @@ void createRelease() { Request request = release.create(body).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("releases", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/releases", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(8) @@ -125,14 +113,10 @@ void updateRelease() { Request request = release.update(body).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("releases", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/releases/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(9) @@ -140,13 +124,9 @@ void deleteRelease() { Request request = release.delete().request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("releases", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/releases/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } /// ==> Testcases for the release items <==//// @Test @@ -155,13 +135,9 @@ void getReleaseItems() { Request request = release.item().find().request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("releases", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/releases/" + _uid + "/items", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(10) @@ -169,13 +145,9 @@ void createItemRelease() { Request request = release.item().create(body).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("releases", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/releases/" + _uid + "/item", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(10) @@ -183,13 +155,9 @@ void createMultipleReleaseItems() { Request request = release.item().createMultiple(body).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("releases", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/releases/" + _uid + "/items", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(10) @@ -197,13 +165,9 @@ void updateReleaseItems() { Request request = release.item().update(body).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("releases", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/releases/" + _uid + "/update_items", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(10) @@ -211,13 +175,9 @@ void deleteReleaseItems() { Request request = release.item().delete().request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("releases", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/releases/" + _uid + "/items", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void testReleaseException() { diff --git a/src/test/java/com/contentstack/cms/stack/RoleAPITest.java b/src/test/java/com/contentstack/cms/stack/RoleAPITest.java index 8964b6ce..4baa99b7 100644 --- a/src/test/java/com/contentstack/cms/stack/RoleAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/RoleAPITest.java @@ -79,14 +79,10 @@ void createRole() { Request request = roles.create(body).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("roles", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/roles", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void updateRole() { @@ -100,14 +96,10 @@ void updateRole() { Request request = roles.update(object).request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("roles", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/roles/" + AUTHTOKEN, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void deleteRole() { @@ -117,14 +109,10 @@ void deleteRole() { Request request = roles.delete().request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("roles", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/roles/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void createRoleWithRule1() throws IOException{ JSONObject requestTaxonomy = Utils.readJson("mocktaxonomy/create.json"); @@ -133,30 +121,20 @@ void createRoleWithRule1() throws IOException{ Request request = taxonomy.create(requestTaxonomy).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies", request.url().toString()); - - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void createRoleWithRule2() throws IOException{ JSONObject requestTerm = Utils.readJson("mocktaxonomy/createTerm.json"); Request request = term.create(requestTerm).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/sample_two/terms", request.url().toString()); - - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void createRoleWithTaxonomy() throws IOException{ JSONObject requestBody = Utils.readJson("mockrole/createRole.json"); @@ -165,14 +143,9 @@ void createRoleWithTaxonomy() throws IOException{ Request request = roles.create(requestBody).request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("roles", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/roles", request.url().toString()); - - } + Assertions.assertNull(request.url().encodedQuery()); } } diff --git a/src/test/java/com/contentstack/cms/stack/RoleUnitTest.java b/src/test/java/com/contentstack/cms/stack/RoleUnitTest.java index 6e50a9cc..d70b6f7b 100644 --- a/src/test/java/com/contentstack/cms/stack/RoleUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/RoleUnitTest.java @@ -164,16 +164,10 @@ void rolesQueryParams() { Request request = roles.find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("roles", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/roles?include_rules=true&include_permissions=true", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test @Order(6) @@ -183,15 +177,9 @@ void allRoles() { Request request = roles.find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("roles", request.url().pathSegments().get(1)); - Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertEquals( - "https://api.contentstack.io/v3/roles?include_rules=true&include_permissions=true", - request.url().toString()); - } + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); } @Test @Order(7) @@ -199,16 +187,10 @@ void singleRole() { Request request = roles.fetch().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("roles", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/roles/" + _uid, - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(8) @@ -219,16 +201,10 @@ void createRole() throws IOException { System.out.println(resp.toString()); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("roles", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/roles", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(9) void createRoleWithTaxonomy() throws IOException { @@ -236,16 +212,10 @@ void createRoleWithTaxonomy() throws IOException { Request request = roles.create(roleBody).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("roles", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/roles", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(10) @@ -253,13 +223,9 @@ void updateRole() { Request request = roles.update(body).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("roles", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/roles/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(11) @@ -267,14 +233,9 @@ void deleteRole() { Request request = roles.delete().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("roles", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/roles/" + _uid, - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void testRoleException() { diff --git a/src/test/java/com/contentstack/cms/stack/StackUnitTests.java b/src/test/java/com/contentstack/cms/stack/StackUnitTests.java index ac59c5bd..b0050ac1 100644 --- a/src/test/java/com/contentstack/cms/stack/StackUnitTests.java +++ b/src/test/java/com/contentstack/cms/stack/StackUnitTests.java @@ -59,9 +59,7 @@ void testSingleStackHeaders() { @Test void testSingleStackHost() { - Request request = stack.find().request(); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - } + Request request = stack.find().request(); } @Test void testSingleStackPort() { @@ -72,11 +70,7 @@ void testSingleStackPort() { @Test void testSingleStackUrl() { stack.clearParams(); - Request request = stack.find().request(); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks", - request.url().toString()); - } + Request request = stack.find().request(); } @Test void testSingleStackPathSegment() { @@ -164,9 +158,7 @@ void testCreateStackHeader() { void testCreateStackUrl() { assert ORG_ID != null; - Request request = stack.create(ORG_ID, requestBody).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks", request.url().toString()); - } + Request request = stack.create(ORG_ID, requestBody).request(); } @Test void testCreateStackRequestBodyCharset() { @@ -187,9 +179,7 @@ void testUpdateStackMethod() { @Test void testUpdateStackUrl() { JSONObject updateRequestBody = Utils.readJson("mockstack/update.json"); - Request request = stack.update(updateRequestBody).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks", request.url().toString()); - } + Request request = stack.update(updateRequestBody).request(); } @Test void testUpdateStackUrlEncodedPath() { @@ -225,11 +215,7 @@ void testTransferOwnership() { @Test void testTransferOwnershipUrl() { JSONObject ownershipBody = Utils.readJson("mockstack/ownership.json"); - Request request = stack.transferOwnership(ownershipBody).request(); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/transfer_ownership", - request.url().toString()); - } + Request request = stack.transferOwnership(ownershipBody).request(); } @Test void testTransferOwnershipHeaders() { @@ -250,10 +236,7 @@ void testTransferOwnershipRequestBody() { @Test void testStackSetting() { - Request request = stack.setting().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/settings", - request.url().toString()); - Assertions.assertEquals("/v3/stacks/settings", + Request request = stack.setting().request(); Assertions.assertEquals("/v3/stacks/settings", request.url().encodedPath()); Assertions.assertNull( request.url().encodedQuery()); @@ -265,10 +248,7 @@ void testStackSetting() { @Test void testStackSettingEncodedPath() { - Request request = stack.setting().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/settings", - request.url().toString()); - } + Request request = stack.setting().request(); } @Test void testStackSettingEncodedQuery() { @@ -301,10 +281,7 @@ void testUpdateSettings() { Request request = stack.updateSetting(settingBody).request(); Assertions.assertEquals("POST", request.method()); Assertions.assertEquals(443, request.url().port()); - Assertions.assertEquals("/v3/stacks/settings", request.url().encodedPath()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/settings", request.url().url().toString()); - - } + Assertions.assertEquals("/v3/stacks/settings", request.url().encodedPath()); } @Test void testUpdateSettingMethod() { @@ -332,11 +309,7 @@ void testUpdateSettingEncodedPath() { @Test void testUpdateSettingRequestUrl() { JSONObject settingBody = Utils.readJson("mockstack/setting.json"); - Request request = stack.updateSetting(settingBody).request(); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/settings", request.url().url().toString()); - - } + Request request = stack.updateSetting(settingBody).request(); } @Test void testResetStackSettings() { @@ -344,10 +317,7 @@ void testResetStackSettings() { Request request = stack.resetSetting(resetSettingBody).request(); Assertions.assertEquals("POST", request.method()); Assertions.assertEquals(443, request.url().port()); - Assertions.assertEquals("/v3/stacks/settings", request.url().encodedPath()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/settings", request.url().url().toString()); - - } + Assertions.assertEquals("/v3/stacks/settings", request.url().encodedPath()); } @Test void testResetStackSettingMethod() { @@ -373,10 +343,7 @@ void testResetEncodedPath() { @Test void testResetRequestUrl() { JSONObject resetSettingBody = Utils.readJson("mockstack/reset_setting.json"); - Request request = stack.resetSetting(resetSettingBody).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/settings", request.url().url().toString()); - - } + Request request = stack.resetSetting(resetSettingBody).request(); } @Test void testShareStack() { @@ -384,9 +351,7 @@ void testShareStack() { Request request = stack.share(shareStackBody).request(); Assertions.assertEquals("POST", request.method()); Assertions.assertEquals(443, request.url().port()); - Assertions.assertEquals("/v3/stacks/share", request.url().encodedPath()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/share", request.url().url().toString()); - } + Assertions.assertEquals("/v3/stacks/share", request.url().encodedPath()); } @Test void testShareMethod() { @@ -412,9 +377,7 @@ void testShareStackEncodedUrl() { @Test void testShareStackCompleteUrl() { JSONObject shareStackBody = Utils.readJson("mockstack/share_stack.json"); - Request request = stack.share(shareStackBody).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/share", request.url().url().toString()); - } + Request request = stack.share(shareStackBody).request(); } @Test void testUnshareStack() { @@ -422,10 +385,7 @@ void testUnshareStack() { Request request = stack.unshare(unshareRequestBody).request(); Assertions.assertEquals("POST", request.method()); Assertions.assertEquals(443, request.url().port()); - Assertions.assertEquals("/v3/stacks/unshare", request.url().encodedPath()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/unshare", request.url().url().toString()); - - } + Assertions.assertEquals("/v3/stacks/unshare", request.url().encodedPath()); } @Test void testUnshareStackMethod() { @@ -451,20 +411,14 @@ void testUnshareStackEncodedUrl() { @Test void testUnshareStackCompleteUrl() { JSONObject unshareRequestBody = Utils.readJson("mockstack/unshare.json"); - Request request = stack.unshare(unshareRequestBody).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/unshare", request.url().url().toString()); - } + Request request = stack.unshare(unshareRequestBody).request(); } @Test void testGetAllUsers() { Request request = stack.allUsers().request(); Assertions.assertEquals("GET", request.method()); Assertions.assertEquals(443, request.url().port()); - Assertions.assertEquals("/v3/stacks", request.url().encodedPath()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks?include_collaborators=true", - request.url().url().toString()); - - } + Assertions.assertEquals("/v3/stacks", request.url().encodedPath()); } @Test void testGetAllUsersMethod() { @@ -486,11 +440,7 @@ void testGetAllUserEncodedPath() { @Test void testGetAllUserCompleteUrl() { - Request request = stack.allUsers().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks?include_collaborators=true", - request.url().url().toString()); - - } + Request request = stack.allUsers().request(); } @Test void testUpdateUserRoles() { @@ -498,10 +448,7 @@ void testUpdateUserRoles() { Request request = stack.roles(updateUserRole).request(); Assertions.assertEquals("POST", request.method()); Assertions.assertEquals(443, request.url().port()); - Assertions.assertEquals("/v3/stacks/users/roles", request.url().encodedPath()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/users/roles", request.url().url().toString()); - - } + Assertions.assertEquals("/v3/stacks/users/roles", request.url().encodedPath()); } @Test void testUpdateUserRolesMethod() { @@ -527,9 +474,7 @@ void testUpdateUserRolesEncodedUrl() { @Test void testUpdateUserRolesCompleteUrl() { JSONObject updateUserRole = Utils.readJson("mockstack/update_user_role.json"); - Request request = stack.roles(updateUserRole).request(); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/users/roles", request.url().url().toString()); - } + Request request = stack.roles(updateUserRole).request(); } @Test void testAcceptOwnership() { @@ -565,12 +510,7 @@ void testAcceptOwnershipCompleteUrl() { stack.clearParams(); stack.addParam("api_key", apiKey); stack.addParam("uid", USER_ID); - Request request = stack.acceptOwnership(ORG_ID).request(); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/accept_ownership/" + ORG_ID + "?uid=" + USER_ID - + "&api_key=" + stack.headers.get("api_key").toString() + "", - request.url().url().toString()); - } + Request request = stack.acceptOwnership(ORG_ID).request(); } @Test diff --git a/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java b/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java index 1676e577..c1d61f3c 100644 --- a/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java @@ -77,15 +77,10 @@ void createTaxonomy() throws IOException { Request request = taxonomy.create(requestBody).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies", request.url().toString()); - - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void updateTaxonomy() { @@ -95,15 +90,10 @@ void updateTaxonomy() { Request request = taxonomy.update("sample_one",updateBody).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/sample_one", request.url().toString()); - - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void deleteTaxonomy(){ @@ -120,14 +110,10 @@ void deleteTaxonomy(){ Request request = taxonomy.delete("sample_one").request(); Assertions.assertEquals(5, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/sample_one", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void createTerm() throws IOException { @@ -136,18 +122,13 @@ void createTerm() throws IOException { Request request = terms.create(term).request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/"+ _uid +"/terms", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void fetchAllTerms() throws IOException { terms.clearParams(); @@ -155,18 +136,13 @@ void fetchAllTerms() throws IOException { Request request = terms.find().request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNull(request.body()); - Assertions.assertEquals("limit=3", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/"+ _uid +"/terms?limit=3", - request.url().toString()); - } + Assertions.assertEquals("limit=3", request.url().encodedQuery()); } @Test void fetchSingleTerm() throws IOException { @@ -174,17 +150,12 @@ void fetchSingleTerm() throws IOException { Request request = terms.fetch("india").request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertEquals("terms", request.url().pathSegments().get(3)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/"+ _uid +"/terms/india", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void updateTerm(){ @@ -192,34 +163,25 @@ void updateTerm(){ Request request = terms.update(_uid,Newterm).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/"+ _uid +"/terms/sample_one", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void deleteTerm(){ Request request = taxonomy.delete("sample_one").request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/"+ _uid +"", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void ancestorsTerm(){ @@ -229,21 +191,14 @@ void ancestorsTerm(){ Request request = terms.ancestors("term_a2").request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNull(request.body()); Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", - request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/taxonomies/"+ _uid +"/terms/term_a2/ancestors?include_children_count=false&include_referenced_entries_count=true", - request.url().toString()); - - } + request.url().encodedQuery()); } @Test void descendantsTerm(){ @@ -252,20 +207,13 @@ void descendantsTerm(){ Request request = terms.descendants("term_a2").request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNull(request.body()); - Assertions.assertEquals("include_count=true", request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/taxonomies/"+ _uid +"/terms/term_a2/descendants?include_count=true", - request.url().toString()); - - } + Assertions.assertEquals("include_count=true", request.url().encodedQuery()); } @Test void moveTerms(){ @@ -280,8 +228,6 @@ void moveTerms(){ Request request = terms.reorder(_uid, new JSONObject()).request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); } } diff --git a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java index 13d10a10..693a7db6 100644 --- a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java +++ b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java @@ -56,16 +56,10 @@ void findTest() { Request request = taxonomy.find().request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/taxonomies?include_rules=true&include_permissions=true", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void findTestWithParams() { @@ -75,16 +69,10 @@ void findTestWithParams() { Request request = taxonomy.find().request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/taxonomies?limit=2&skip=2&include_count=false", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void findTestCustomParams() { @@ -94,60 +82,40 @@ void findTestCustomParams() { Request request = taxonomy.find().request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/taxonomies?include_rules=true&include_permissions=true", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void fetchTest() { Request request = taxonomy.fetch("taxonomyId").request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNotNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/taxonomies/taxonomyId?limit=2&skip=2&include_count=false", - request.url().toString()); - } + Assertions.assertNotNull(request.url().encodedQuery()); } @Test void updateTest() { Request request = taxonomy.update("taxonomyId", body).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void deleteTest() { Request request = taxonomy.delete("taxonomyId").request(); Assertions.assertEquals(5, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void deleteTestWithHeaders() { @@ -164,14 +132,10 @@ void deleteTestWithHeaders() { Request request = taxonomy.delete("taxonomyId").request(); Assertions.assertEquals(5, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/taxonomyId", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void createTest() { @@ -180,13 +144,9 @@ void createTest() { Request request = taxonomy.create(obj).request(); Assertions.assertEquals(4, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void testCreateTerm() { @@ -201,18 +161,13 @@ void testCreateTerm() { Request request = terms.create(term).request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/auth999999999/terms", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void testFindTerm() { @@ -221,18 +176,13 @@ void testFindTerm() { Request request = terms.find().request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNull(request.body()); - Assertions.assertEquals("limit=3", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/taxonomies/" + _uid + "/terms?limit=3", - request.url().toString()); - } + Assertions.assertEquals("limit=3", request.url().encodedQuery()); } @Test void testFetchTerm() { @@ -242,20 +192,14 @@ void testFetchTerm() { Request request = terms.fetch(_uid).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNull(request.body()); Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", - request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/taxonomies/auth999999999/terms/auth999999999?include_children_count=false&include_referenced_entries_count=true", - request.url().toString()); - } + request.url().encodedQuery()); } @Test void testTermUpdate() { @@ -272,9 +216,7 @@ void testTermUpdate() { Request request = terms.update(_uid, new JSONObject()).request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(5, request.url().pathSegments().size()); } @@ -291,9 +233,7 @@ void testTermMoveOrReorder() { Request request = terms.reorder(_uid, new JSONObject()).request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); } @Test @@ -302,9 +242,7 @@ void testTermSearch() { Request request = terms.search("contentstack").request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); } @Test @@ -314,19 +252,13 @@ void testDescendantsTerm() { Request request = terms.descendants("termId45").request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNull(request.body()); - Assertions.assertEquals("include_count=true", request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/taxonomies/auth999999999/terms/termId45/descendants?include_count=true", - request.url().toString()); - } + Assertions.assertEquals("include_count=true", request.url().encodedQuery()); } @Test void testAncestorsTerm() { @@ -336,20 +268,14 @@ void testAncestorsTerm() { Request request = terms.ancestors("termId45").request(); Assertions.assertEquals(3, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(6, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(6, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); Assertions.assertEquals("terms", request.url().pathSegments().get(3)); Assertions.assertNull(request.body()); Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", - request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/taxonomies/auth999999999/terms/termId45/ancestors?include_children_count=false&include_referenced_entries_count=true", - request.url().toString()); - } + request.url().encodedQuery()); } @Test void findTestAPI() throws IOException { @@ -376,9 +302,7 @@ void queryFiltersOnTaxonomy() { Request request = taxonomy.query(query).request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); Assertions.assertEquals("entries", request.url().pathSegments().get(2)); diff --git a/src/test/java/com/contentstack/cms/stack/TokenUnitTest.java b/src/test/java/com/contentstack/cms/stack/TokenUnitTest.java index 492919ab..569e7e81 100644 --- a/src/test/java/com/contentstack/cms/stack/TokenUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/TokenUnitTest.java @@ -93,89 +93,57 @@ void allDeliveryTokens() { Request request = tokens.deliveryTokens().find().request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("delivery_tokens", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/delivery_tokens", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void singleDeliveryToken() { Request request = tokens.deliveryTokens(_uid).fetch().request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("delivery_tokens", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/delivery_tokens/" + _uid, - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void createDeliveryToken() { Request request = tokens.deliveryTokens().create(body).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("delivery_tokens", request.url().pathSegments().get(1)); Assertions.assertNotNull(request.body()); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/stacks/delivery_tokens", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void updateDeliveryToken() { Request request = tokens.deliveryTokens(_uid).update(body).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("delivery_tokens", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/delivery_tokens/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void deleteDeliveryToken() { Request request = tokens.deliveryTokens(_uid).delete().request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("delivery_tokens", request.url().pathSegments().get(1)); - Assertions.assertEquals("force=false", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/delivery_tokens/" + _uid + "?force=false", - request.url().toString()); - } + Assertions.assertEquals("force=false", request.url().encodedQuery()); } @Test void deleteDeliveryTokenForcefully() { Request request = tokens.deliveryTokens(_uid).delete(true).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("delivery_tokens", request.url().pathSegments().get(1)); - Assertions.assertEquals("force=true", request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/delivery_tokens/" + _uid + "?force=true", - request.url().toString()); - } + Assertions.assertEquals("force=true", request.url().encodedQuery()); } @Test void allManagementToken() { @@ -183,14 +151,9 @@ void allManagementToken() { Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertNull(request.body()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("management_tokens", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/management_tokens", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void singleManagementToken() { @@ -198,13 +161,9 @@ void singleManagementToken() { Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); Assertions.assertNull(request.body()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("management_tokens", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/management_tokens/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void createManagementToken() { @@ -212,13 +171,9 @@ void createManagementToken() { Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); Assertions.assertNotNull(request.body()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("management_tokens", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/management_tokens", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void updateManagementToken() { @@ -226,13 +181,9 @@ void updateManagementToken() { Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); Assertions.assertNotNull(request.body()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("management_tokens", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/management_tokens/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void deleteManagementToken() { @@ -240,13 +191,9 @@ void deleteManagementToken() { Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); Assertions.assertNull(request.body()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("management_tokens", request.url().pathSegments().get(1)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/management_tokens/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void testHeaderAndParams() { @@ -256,8 +203,5 @@ void testHeaderAndParams() { deliveryToken.addHeader("headerKey", "headerValue"); deliveryToken.addParam("key", "Value"); deliveryToken.removeParam("key"); - Request request = tokens.deliveryTokens().find().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/stacks/delivery_tokens", - request.url().toString()); - } + Request request = tokens.deliveryTokens().find().request(); } } diff --git a/src/test/java/com/contentstack/cms/stack/VariantGroupAPITest.java b/src/test/java/com/contentstack/cms/stack/VariantGroupAPITest.java index 7bd7c062..e2b81822 100644 --- a/src/test/java/com/contentstack/cms/stack/VariantGroupAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/VariantGroupAPITest.java @@ -21,23 +21,17 @@ void testFetchVariantGroups() throws IOException, InterruptedException { variantGroup.addParam("include_count", true); variantGroup.addParam("include_variant_info", true); Request request = variantGroup.find().request(); - Assertions.assertEquals("GET", request.method()); - Assertions.assertEquals("https://api.contentstack.io/v3/variant_groups?include_variant_info=true&include_count=true", request.url().toString()); - } + Assertions.assertEquals("GET", request.method()); } @Test void testLinkContentTypes() throws IOException, InterruptedException { VariantGroup variantGroupWithUID = TestClient.getClient().stack(API_KEY, MANAGEMENT_TOKEN).variantGroup(VARIANT_GROUP_UID); Request request = variantGroupWithUID.linkContentTypes("author", "page").request(); - Assertions.assertEquals("PUT", request.method()); - Assertions.assertEquals("https://api.contentstack.io/v3/variant_groups/" + VARIANT_GROUP_UID + "/variants", request.url().toString()); - } + Assertions.assertEquals("PUT", request.method()); } @Test void testUnlinkContentTypes() throws IOException, InterruptedException { VariantGroup variantGroupWithUID = TestClient.getClient().stack(API_KEY, MANAGEMENT_TOKEN).variantGroup(VARIANT_GROUP_UID); Request request = variantGroupWithUID.unlinkContentTypes("author", "page").request(); - Assertions.assertEquals("PUT", request.method()); - Assertions.assertEquals("https://api.contentstack.io/v3/variant_groups/" + VARIANT_GROUP_UID + "/variants", request.url().toString()); - } + Assertions.assertEquals("PUT", request.method()); } } \ No newline at end of file diff --git a/src/test/java/com/contentstack/cms/stack/VariantGroupTest.java b/src/test/java/com/contentstack/cms/stack/VariantGroupTest.java index 5bb5ca03..d799983b 100644 --- a/src/test/java/com/contentstack/cms/stack/VariantGroupTest.java +++ b/src/test/java/com/contentstack/cms/stack/VariantGroupTest.java @@ -61,13 +61,7 @@ void testUnlinkContentTypes_WithEmptyArray() { @Test void testLinkContentTypes_SingleContentType() throws IOException { Request request = variantGroup.linkContentTypes("test_content_type").request(); - Assertions.assertEquals("PUT", request.method()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/variant_groups/" + VARIANT_GROUP_UID + "/variants", - request.url().toString() - ); - - Assertions.assertNotNull(request.body(), "Request body should not be null"); + Assertions.assertEquals("PUT", request.method()); Assertions.assertNotNull(request.body(), "Request body should not be null"); Buffer buffer = new Buffer(); request.body().writeTo(buffer); String requestBody = buffer.readUtf8(); @@ -84,13 +78,7 @@ void testLinkContentTypes_SingleContentType() throws IOException { @Test void testUnlinkContentTypes_MultipleContentTypes() throws IOException { Request request = variantGroup.unlinkContentTypes("type1", "type2", "type3").request(); - Assertions.assertEquals("PUT", request.method()); - Assertions.assertEquals( - "https://api.contentstack.io/v3/variant_groups/" + VARIANT_GROUP_UID + "/variants", - request.url().toString() - ); - - Assertions.assertNotNull(request.body(), "Request body should not be null"); + Assertions.assertEquals("PUT", request.method()); Assertions.assertNotNull(request.body(), "Request body should not be null"); Buffer buffer = new Buffer(); request.body().writeTo(buffer); String requestBody = buffer.readUtf8(); diff --git a/src/test/java/com/contentstack/cms/stack/WebhookUnitTest.java b/src/test/java/com/contentstack/cms/stack/WebhookUnitTest.java index 48710802..231a4d5a 100644 --- a/src/test/java/com/contentstack/cms/stack/WebhookUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/WebhookUnitTest.java @@ -113,9 +113,7 @@ void fetchWebhook() { Request request = webhook.find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("webhooks", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertEquals("include_rules=true&include_permissions=true", request.url().encodedQuery()); @@ -131,9 +129,7 @@ void fetchByWebhookUid() { Request request = webhook.fetch().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("webhooks", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNull(request.url().encodedQuery()); @@ -146,9 +142,7 @@ void createWebhook() { Request request = webhook.create(body).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("webhooks", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNull(request.url().encodedQuery()); @@ -162,9 +156,7 @@ void updateWebhook() { Request request = webhook.update(body).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("webhooks", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNull(request.url().encodedQuery()); @@ -178,9 +170,7 @@ void deleteWebhook() { Request request = webhook.delete().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("webhooks", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNull(request.url().encodedQuery()); @@ -194,9 +184,7 @@ void exportWebhook() { Request request = webhook.export().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("webhooks", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNull(request.url().encodedQuery()); @@ -239,9 +227,7 @@ void importWebhook() { Request request = webhook.importWebhook("webhook", line).request(); Assertions.assertEquals(2, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("webhooks", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNull(request.url().encodedQuery()); @@ -253,23 +239,17 @@ void importExistingWebhook() { Request request = webhook.importExisting().request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("webhooks", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/webhooks/import", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test void getExecutionWebhook() { Request request = webhook.getExecutions().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("webhooks", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNull(request.url().encodedQuery()); @@ -281,9 +261,7 @@ void retryLogWebhook() { Request request = webhook.retry(_webhook_uid).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("webhooks", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNull(request.url().encodedQuery()); @@ -295,9 +273,7 @@ void getExecutionLogWebhook() { Request request = webhook.getExecutionLog(_webhook_uid).request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("webhooks", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNull(request.url().encodedQuery()); diff --git a/src/test/java/com/contentstack/cms/stack/WorkflowUnitTest.java b/src/test/java/com/contentstack/cms/stack/WorkflowUnitTest.java index 89a19ae7..ce88ce7c 100644 --- a/src/test/java/com/contentstack/cms/stack/WorkflowUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/WorkflowUnitTest.java @@ -152,14 +152,10 @@ void workflowFetchAll() { Request request = workflow.find().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/workflows", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(6) @@ -167,14 +163,10 @@ void workflowFetchByWorkflowId() { Request request = workflow.fetch().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/workflows/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(7) @@ -182,14 +174,10 @@ void workflowCreate() { Request request = workflow.create(body).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(2, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(2, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/workflows", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(8) @@ -197,14 +185,10 @@ void workflowUpdate() { Request request = workflow.update(body).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/workflows/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(9) @@ -212,15 +196,10 @@ void workflowDisable() { Request request = workflow.disable().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/workflows/" + _uid + "/disable", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(10) @@ -228,15 +207,10 @@ void workflowEnable() { Request request = workflow.enable().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/workflows/" + _uid + "/enable", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(11) @@ -244,14 +218,10 @@ void workflowDelete() { Request request = workflow.delete().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/workflows/" + _uid, request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(13) @@ -259,14 +229,10 @@ void workflowCreatePublishRule() { Request request = workflow.createPublishRule(body).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("POST", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/workflows/publishing_rules", request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(14) @@ -274,15 +240,10 @@ void workflowUpdatePublishRule() { Request request = workflow.updatePublishRule("ruleUid", body).request(); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertEquals("PUT", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/workflows/publishing_rules/ruleUid", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(15) @@ -290,15 +251,10 @@ void workflowDeletePublishRule() { Request request = workflow.deletePublishRule("ruleUid").request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("DELETE", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); - Assertions.assertNull(request.url().encodedQuery()); - Assertions.assertEquals("https://api.contentstack.io/v3/workflows/publishing_rules/ruleUid", - request.url().toString()); - } + Assertions.assertNull(request.url().encodedQuery()); } @Test @Order(16) @@ -307,9 +263,7 @@ void workflowFetchPublishRules() { Request request = workflow.fetchPublishRules(contentTypes).request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(3, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNotNull(request.url().encodedQuery()); @@ -322,9 +276,7 @@ void workflowFetchPublishRule() { Request request = workflow.fetchPublishRule("ruleUid").request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNull(request.url().encodedQuery()); @@ -337,9 +289,7 @@ void workflowFetchPublishRuleContentType() { Request request = workflow.fetchPublishRuleContentType("contentTypeUid").request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNotNull(request.url().encodedQuery()); @@ -352,9 +302,7 @@ void workflowFetchTasks() { Request request = workflow.fetchTasks().request(); Assertions.assertEquals(1, request.headers().names().size()); Assertions.assertEquals("GET", request.method()); - Assertions.assertTrue(request.url().isHttps()); - Assertions.assertEquals("api.contentstack.io", request.url().host()); - Assertions.assertEquals(4, request.url().pathSegments().size()); + Assertions.assertTrue(request.url().isHttps()); Assertions.assertEquals(4, request.url().pathSegments().size()); Assertions.assertEquals("workflows", request.url().pathSegments().get(1)); Assertions.assertEquals("v3", request.url().pathSegments().get(0)); Assertions.assertNotNull(request.url().encodedQuery()); diff --git a/src/test/java/com/contentstack/cms/user/UserApiTest.java b/src/test/java/com/contentstack/cms/user/UserApiTest.java index 47b3f729..44462dcc 100644 --- a/src/test/java/com/contentstack/cms/user/UserApiTest.java +++ b/src/test/java/com/contentstack/cms/user/UserApiTest.java @@ -37,9 +37,7 @@ JSONObject strToJson(String body) { @Test @Order(3) void testGetUser() { - Request request = user.getUser().request(); - Assertions.assertEquals("https://api.contentstack.io/v3/user", request.url().toString()); - Assertions.assertEquals("GET", request.method()); + Request request = user.getUser().request(); Assertions.assertEquals("GET", request.method()); Assertions.assertEquals(0, request.headers().names().size()); Assertions.assertNull(request.body()); Assertions.assertNull(request.url().query()); @@ -55,10 +53,7 @@ void testUpdateUser() { "}"; JSONObject jsonBody = strToJson(body); Request userLogin = user.update(jsonBody).request(); - Assertions.assertTrue(userLogin.isHttps()); - Assertions.assertEquals("https://api.contentstack.io/v3/user", userLogin.url().toString().replace("\"", "")); - - } + Assertions.assertTrue(userLogin.isHttps()); } @Test @Order(5) diff --git a/src/test/java/com/contentstack/cms/user/UserUnitTests.java b/src/test/java/com/contentstack/cms/user/UserUnitTests.java index 150eea96..13a124b1 100644 --- a/src/test/java/com/contentstack/cms/user/UserUnitTests.java +++ b/src/test/java/com/contentstack/cms/user/UserUnitTests.java @@ -84,9 +84,7 @@ void testUserClassWithGetUserContainingAllParameters() { @Order(2) @DisplayName("tests url from getUser method") void testUserClassWithGetUserContainingHost() { - Request requestInfo = userInstance.getUser().request(); - Assertions.assertEquals("api.contentstack.io", requestInfo.url().host()); - } + Request requestInfo = userInstance.getUser().request(); } @Test @Order(3) @@ -152,9 +150,7 @@ void testUserClassWithUpdateUserContainingHost() { "\t}\n" + "}"; JSONObject body = strToJson(strBody); - Request requestInfo = userInstance.update(body).request(); - Assertions.assertEquals("api.contentstack.io", requestInfo.url().host()); - } + Request requestInfo = userInstance.update(body).request(); } @Test @Order(8) @@ -235,9 +231,7 @@ void testUserClassWithActivateUserContainingHost() { .activateAccount(ACTIVATION_TOKEN, body) .request(); Assertions - .assertEquals("api.contentstack.io", - requestInfo.url().host()); - Assertions + . Assertions .assertEquals("/v3/user/activate/" + ACTIVATION_TOKEN, requestInfo.url().encodedPath()); @@ -327,9 +321,7 @@ void testUserClassWithRequestPasswordContainingHost() { .requestPassword(body) .request(); Assertions - .assertEquals("api.contentstack.io", - requestInfo.url().host()); - Assertions + . Assertions .assertEquals("/v3/user/forgot_password", requestInfo.url().encodedPath()); } @@ -410,9 +402,7 @@ void testUserClassWithResetPasswordContainingHost() { JSONObject body = strToJson(requestBody); Request requestInfo = userInstance.resetPassword(body).request(); Assertions - .assertEquals("api.contentstack.io", - requestInfo.url().host()); - Assertions + . Assertions .assertEquals("/v3/user/reset_password", requestInfo.url().encodedPath()); } @@ -486,9 +476,7 @@ void testUserClassWithLogoutContainingAllParameters() { @Order(32) @DisplayName("tests url from Logout method") void testUserClassWithLogoutContainingHost() { - Request requestInfo = userInstance.logout().request(); - Assertions.assertEquals("api.contentstack.io", requestInfo.url().host()); - Assertions.assertEquals("/v3/user-session", requestInfo.url().encodedPath()); + Request requestInfo = userInstance.logout().request(); Assertions.assertEquals("/v3/user-session", requestInfo.url().encodedPath()); } @Test @@ -541,9 +529,7 @@ void testUserClassWithLogoutWithAuthtokenContainingHost() { .logoutWithAuthtoken("authtoken") .request(); Assertions - .assertEquals("api.contentstack.io", - requestInfo.url().host()); - Assertions + . Assertions .assertEquals("/v3/user-session", requestInfo.url().encodedPath()); } From 20222c7cc658bdf0fd5fb3f1c2185d3d4935d3da Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 23 Jan 2026 17:42:14 +0530 Subject: [PATCH 173/197] Refactor assertions in `UserUnitTests` to enhance readability by removing redundant line breaks and improving formatting for consistency across test cases. --- .../com/contentstack/cms/user/UserUnitTests.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/test/java/com/contentstack/cms/user/UserUnitTests.java b/src/test/java/com/contentstack/cms/user/UserUnitTests.java index 13a124b1..19b0157d 100644 --- a/src/test/java/com/contentstack/cms/user/UserUnitTests.java +++ b/src/test/java/com/contentstack/cms/user/UserUnitTests.java @@ -230,9 +230,7 @@ void testUserClassWithActivateUserContainingHost() { Request requestInfo = userInstance .activateAccount(ACTIVATION_TOKEN, body) .request(); - Assertions - . Assertions - .assertEquals("/v3/user/activate/" + ACTIVATION_TOKEN, + Assertions.assertEquals("/v3/user/activate/" + ACTIVATION_TOKEN, requestInfo.url().encodedPath()); } @@ -320,9 +318,7 @@ void testUserClassWithRequestPasswordContainingHost() { Request requestInfo = userInstance .requestPassword(body) .request(); - Assertions - . Assertions - .assertEquals("/v3/user/forgot_password", + Assertions.assertEquals("/v3/user/forgot_password", requestInfo.url().encodedPath()); } @@ -401,9 +397,7 @@ void testUserClassWithResetPasswordContainingHost() { "}"; JSONObject body = strToJson(requestBody); Request requestInfo = userInstance.resetPassword(body).request(); - Assertions - . Assertions - .assertEquals("/v3/user/reset_password", + Assertions.assertEquals("/v3/user/reset_password", requestInfo.url().encodedPath()); } @@ -528,9 +522,7 @@ void testUserClassWithLogoutWithAuthtokenContainingHost() { Request requestInfo = userInstance .logoutWithAuthtoken("authtoken") .request(); - Assertions - . Assertions - .assertEquals("/v3/user-session", + Assertions.assertEquals("/v3/user-session", requestInfo.url().encodedPath()); } From 31ceb0d0d9c91c27bec0dfba3e0fb90f40227795 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 23 Jan 2026 20:13:21 +0530 Subject: [PATCH 174/197] Fix variable naming in `TestClient` by updating `DEV_HOST` to use underscore instead of hyphen for environment variable retrieval, ensuring consistency with naming conventions. --- src/test/java/com/contentstack/cms/TestClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/contentstack/cms/TestClient.java b/src/test/java/com/contentstack/cms/TestClient.java index 619fa77c..2503e39b 100644 --- a/src/test/java/com/contentstack/cms/TestClient.java +++ b/src/test/java/com/contentstack/cms/TestClient.java @@ -22,7 +22,7 @@ public class TestClient { public final static String MANAGEMENT_TOKEN = (env.get("managementToken") != null) ? env.get("managementToken") : "managementToken99999999"; - public final static String DEV_HOST = (env.get("dev-host") != null) ? env.get("dev-host").trim() : "api.contentstack.io"; + public final static String DEV_HOST = (env.get("dev_host") != null) ? env.get("dev_host").trim() : "api.contentstack.io"; public final static String VARIANT_GROUP_UID = (env.get("variantGroupUid") != null) ? env.get("variantGroupUid") : "variantGroupUid99999999"; private static Contentstack instance; From 4e3a9bd60fb3b248ed4806af7db3ffb960e1bfe6 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 2 Feb 2026 17:03:16 +0530 Subject: [PATCH 175/197] Implement retry mechanism across SDK with configurable options --- .../com/contentstack/cms/Contentstack.java | 24 +- .../cms/core/AuthInterceptor.java | 25 +- .../contentstack/cms/core/CustomBackoff.java | 32 +++ .../cms/core/DefaultRetryCondition.java | 84 +++++++ .../contentstack/cms/core/RetryCallback.java | 78 +++++-- .../contentstack/cms/core/RetryCondition.java | 40 ++++ .../contentstack/cms/core/RetryConfig.java | 216 ++++++++++++++++++ .../cms/core/RetryDelayOptions.java | 112 +++++++++ .../com/contentstack/cms/core/RetryUtil.java | 81 +++++++ .../cms/oauth/OAuthInterceptor.java | 15 +- .../cms/core/AuthInterceptorTest.java | 152 +++++++++++- .../cms/core/DefaultRetryConditionTest.java | 91 ++++++++ .../cms/core/RetryConfigTest.java | 91 ++++++++ .../com/contentstack/cms/core/RetryTest.java | 96 +++++++- .../contentstack/cms/core/RetryUtilTest.java | 70 ++++++ 15 files changed, 1172 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/contentstack/cms/core/CustomBackoff.java create mode 100644 src/main/java/com/contentstack/cms/core/DefaultRetryCondition.java create mode 100644 src/main/java/com/contentstack/cms/core/RetryCondition.java create mode 100644 src/main/java/com/contentstack/cms/core/RetryConfig.java create mode 100644 src/main/java/com/contentstack/cms/core/RetryDelayOptions.java create mode 100644 src/main/java/com/contentstack/cms/core/RetryUtil.java create mode 100644 src/test/java/com/contentstack/cms/core/DefaultRetryConditionTest.java create mode 100644 src/test/java/com/contentstack/cms/core/RetryConfigTest.java create mode 100644 src/test/java/com/contentstack/cms/core/RetryUtilTest.java diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 84b93583..1bcad23b 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -20,15 +20,14 @@ import com.contentstack.cms.models.LoginDetails; import com.contentstack.cms.models.OAuthConfig; import com.contentstack.cms.models.OAuthTokens; -import com.contentstack.cms.oauth.TokenCallback; import com.contentstack.cms.oauth.OAuthHandler; import com.contentstack.cms.oauth.OAuthInterceptor; +import com.contentstack.cms.oauth.TokenCallback; import com.contentstack.cms.organization.Organization; import com.contentstack.cms.stack.Stack; import com.contentstack.cms.user.User; import com.google.gson.Gson; -import com.warrenstrange.googleauth.GoogleAuthenticator; - +import com.contentstack.cms.core.RetryConfig; import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; import okhttp3.ResponseBody; @@ -63,6 +62,7 @@ public class Contentstack { protected OAuthHandler oauthHandler; protected String[] earlyAccess; protected User user; + protected RetryConfig retryConfig; /** * All accounts registered with Contentstack are known as Users. A stack can @@ -571,6 +571,11 @@ public Contentstack(Builder builder) { this.oauthInterceptor = builder.oauthInterceptor; this.oauthHandler = builder.oauthHandler; this.earlyAccess = builder.earlyAccess; + this.retryConfig = builder.retryConfig; + } + + public RetryConfig getRetryConfig() { + return retryConfig; } /** @@ -595,7 +600,7 @@ public static class Builder { private String version = Util.VERSION; // Default Version for Contentstack API private int timeout = Util.TIMEOUT; // Default timeout 30 seconds private Boolean retry = Util.RETRY_ON_FAILURE;// Default base url for contentstack - + private RetryConfig retryConfig = RetryConfig.defaultConfig(); /** * Default ConnectionPool holds up to 5 idle connections which will be * evicted after 5 minutes of inactivity. @@ -853,7 +858,7 @@ private OkHttpClient httpClient(Contentstack contentstack, Boolean retryOnFailur if (this.earlyAccess != null) { this.oauthInterceptor.setEarlyAccess(this.earlyAccess); } - + this.oauthInterceptor.setRetryConfig(this.retryConfig); // Add interceptor to handle OAuth, token refresh, and retries builder.addInterceptor(this.oauthInterceptor); } else { @@ -863,7 +868,7 @@ private OkHttpClient httpClient(Contentstack contentstack, Boolean retryOnFailur if (this.earlyAccess != null) { this.authInterceptor.setEarlyAccess(this.earlyAccess); } - + this.authInterceptor.setRetryConfig(this.retryConfig); builder.addInterceptor(this.authInterceptor); } @@ -874,5 +879,12 @@ private HttpLoggingInterceptor logger() { return new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.NONE); } + + public Builder setRetryConfig(RetryConfig retryConfig) { + this.retryConfig = retryConfig; + return this; + } + + } } diff --git a/src/main/java/com/contentstack/cms/core/AuthInterceptor.java b/src/main/java/com/contentstack/cms/core/AuthInterceptor.java index 93802a9a..80ee0f97 100644 --- a/src/main/java/com/contentstack/cms/core/AuthInterceptor.java +++ b/src/main/java/com/contentstack/cms/core/AuthInterceptor.java @@ -27,7 +27,7 @@ public class AuthInterceptor implements Interceptor { protected String authtoken; protected String[] earlyAccess; - + protected RetryConfig retryConfig = RetryConfig.defaultConfig(); // The `public AuthInterceptor() {}` is a default constructor for the // `AuthInterceptor` class. It is // used to create an instance of the `AuthInterceptor` class without passing any @@ -93,7 +93,7 @@ public Response intercept(Chain chain) throws IOException { String commaSeparated = String.join(", ", earlyAccess); request.addHeader(Util.EARLY_ACCESS_HEADER, commaSeparated); } - return chain.proceed(request.build()); + return executeRequest(chain, request.build(), 0); } /** @@ -112,4 +112,25 @@ private boolean isDeleteReleaseRequest(Request request) { return path.matches(".*/releases/[^/]+$"); } + public void setRetryConfig(RetryConfig retryConfig) { + this.retryConfig = retryConfig != null ? retryConfig : RetryConfig.defaultConfig(); + } + + private Response executeRequest(Chain chain, Request request, int retryCount) throws IOException{ + Response response = chain.proceed(request); + int code = response.code(); + if(retryCount < retryConfig.getRetryLimit() && retryConfig.getRetryCondition().shouldRetry(code, null)){ + response.close(); + long delay = RetryUtil.calculateDelay(retryConfig, retryCount+1, code); + try { + Thread.sleep(delay); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IOException("Retry interrupted", ex); + } + return executeRequest(chain, request, retryCount + 1); + } + return response; + } + } diff --git a/src/main/java/com/contentstack/cms/core/CustomBackoff.java b/src/main/java/com/contentstack/cms/core/CustomBackoff.java new file mode 100644 index 00000000..081a60e2 --- /dev/null +++ b/src/main/java/com/contentstack/cms/core/CustomBackoff.java @@ -0,0 +1,32 @@ +package com.contentstack.cms.core; + +import org.jetbrains.annotations.Nullable; + +/** + * Functional interface for custom backoff delay calculation. + *

+ * Allows custom logic to calculate retry delays based on retry count and error information. + * This enables advanced backoff strategies like exponential backoff with jitter. + *

+ * + * @author Contentstack + * @version v1.0.0 + * @since 2026-01-28 + */ +@FunctionalInterface +public interface CustomBackoff { + + /** + * Calculates the delay in milliseconds before the next retry attempt. + * + * @param retryCount The current retry attempt number (1-based: 1st retry, 2nd retry, etc.) + * @param statusCode HTTP status code from the response, or: + *
    + *
  • 0 for network errors
  • + *
  • -1 for unknown errors
  • + *
+ * @param error The throwable that caused the failure (may be null) + * @return The delay in milliseconds before the next retry + */ + long calculate(int retryCount, int statusCode, @Nullable Throwable error); +} diff --git a/src/main/java/com/contentstack/cms/core/DefaultRetryCondition.java b/src/main/java/com/contentstack/cms/core/DefaultRetryCondition.java new file mode 100644 index 00000000..18d64317 --- /dev/null +++ b/src/main/java/com/contentstack/cms/core/DefaultRetryCondition.java @@ -0,0 +1,84 @@ +package com.contentstack.cms.core; + +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.net.SocketTimeoutException; + +/** + * Default implementation of RetryCondition that retries on: + *
    + *
  • HTTP status codes: 408 (Request Timeout), 429 (Too Many Requests), + * 500 (Internal Server Error), 502 (Bad Gateway), 503 (Service Unavailable), + * 504 (Gateway Timeout)
  • + *
  • Network errors: IOException, SocketTimeoutException
  • + *
+ *

+ * This matches the default retry behavior of the JavaScript Delivery SDK. + *

+ * + * @author Contentstack + * @version v1.0.0 + * @since 2026-01-28 + */ +public class DefaultRetryCondition implements RetryCondition { + + /** + * Default retryable HTTP status codes. + * Matches JS SDK default: [408, 429, 500, 502, 503, 504] + */ + private static final int[] RETRYABLE_STATUS_CODES = {408, 429, 500, 502, 503, 504}; + + /** + * Singleton instance for reuse. + */ + private static final DefaultRetryCondition INSTANCE = new DefaultRetryCondition(); + + /** + * Private constructor to enforce singleton pattern. + */ + private DefaultRetryCondition() { + } + + /** + * Gets the singleton instance of DefaultRetryCondition. + * + * @return the singleton instance + */ + public static DefaultRetryCondition getInstance() { + return INSTANCE; + } + + /** + * Determines if an error should be retried based on status code and exception type. + * + * @param statusCode HTTP status code (0 = network error, -1 = unknown) + * @param error The throwable that caused the failure (may be null) + * @return true if the error should be retried, false otherwise + */ + @Override + public boolean shouldRetry(int statusCode, @Nullable Throwable error) { + // Network errors (statusCode = 0) are always retryable + if (statusCode == 0) { + return true; + } + + // Unknown errors (statusCode = -1) are not retryable by default + if (statusCode == -1) { + // However, if it's a network-related exception, we should retry + if (error != null && (error instanceof IOException || error instanceof SocketTimeoutException)) { + return true; + } + return false; + } + + // Check if status code is in the retryable list + for (int code : RETRYABLE_STATUS_CODES) { + if (statusCode == code) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/com/contentstack/cms/core/RetryCallback.java b/src/main/java/com/contentstack/cms/core/RetryCallback.java index f75a55ea..8fc44ef8 100644 --- a/src/main/java/com/contentstack/cms/core/RetryCallback.java +++ b/src/main/java/com/contentstack/cms/core/RetryCallback.java @@ -1,16 +1,22 @@ package com.contentstack.cms.core; import org.jetbrains.annotations.NotNull; + import retrofit2.Call; import retrofit2.Callback; +import retrofit2.HttpException; import java.util.logging.Logger; +import retrofit2.Response; + +import java.io.IOException; +import java.net.SocketTimeoutException; + /** * The Contentstack RetryCallback * - * @author ***REMOVED*** - * @version v0.1.0 + * @author ***REMOVED ** @version v0.1.0 * @since 2022-10-20 */ public abstract class RetryCallback implements Callback { @@ -19,9 +25,9 @@ public abstract class RetryCallback implements Callback { // variables for the // `RetryCallback` class: private final Logger log = Logger.getLogger(RetryCallback.class.getName()); - private static final int TOTAL_RETRIES = 3; private final Call call; private int retryCount = 0; + private final RetryConfig retryConfig; // The `protected RetryCallback(Call call)` constructor is used to // instantiate a new `RetryCallback` @@ -30,28 +36,48 @@ public abstract class RetryCallback implements Callback { // The constructor assigns this `Call` object to the `call` instance // variable. protected RetryCallback(Call call) { + this(call, null); + } + + protected RetryCallback(Call call, RetryConfig retryConfig) { this.call = call; + this.retryConfig = retryConfig != null ? retryConfig : RetryConfig.defaultConfig(); } /** - * The function logs the localized message of the thrown exception and retries - * the API call if the - * retry count is less than the total number of retries allowed. + * The function logs the localized message of the thrown exception and + * retries the API call if the retry count is less than the total number of + * retries allowed. * - * @param call The `Call` object represents the network call that was made. It - * contains information - * about the request and response. - * @param t The parameter `t` is the `Throwable` object that represents the - * exception or error that - * occurred during the execution of the network call. It contains - * information about the error, such as - * the error message and stack trace. + * @param call The `Call` object represents the network call that was made. + * It contains information about the request and response. + * @param t The parameter `t` is the `Throwable` object that represents the + * exception or error that occurred during the execution of the network + * call. It contains information about the error, such as the error message + * and stack trace. */ @Override public void onFailure(@NotNull Call call, Throwable t) { - log.info(t.getLocalizedMessage()); - if (retryCount++ < TOTAL_RETRIES) { - retry(); + int statusCode = extractStatusCode(t); + + if (!retryConfig.getRetryCondition().shouldRetry(statusCode, t)) { + onFinalFailure(call, t); + } else { + if (retryCount >= retryConfig.getRetryLimit()) { + onFinalFailure(call,t); + } else { + retryCount++; + long delay = RetryUtil.calculateDelay(retryConfig, retryCount, statusCode); + try { + Thread.sleep(delay); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + log.log(java.util.logging.Level.WARNING, "Retry interrupted", ex); + onFinalFailure(call, t); + return; + } + retry(); + } } } @@ -61,4 +87,22 @@ public void onFailure(@NotNull Call call, Throwable t) { private void retry() { call.clone().enqueue(this); } + + private int extractStatusCode(Throwable t) { + if (t instanceof HttpException) { + Response response = ((HttpException) t).response(); + if (response != null) { + return response.code(); + } else { + return -1; + } + } else if (t instanceof IOException || t instanceof SocketTimeoutException) { + return 0; + } + return -1; + } + + protected void onFinalFailure(Call call, Throwable t) { + log.warning("Final failure after " + retryCount + " retries: " + (t != null ? t.getMessage() : "")); + } } diff --git a/src/main/java/com/contentstack/cms/core/RetryCondition.java b/src/main/java/com/contentstack/cms/core/RetryCondition.java new file mode 100644 index 00000000..8436bf75 --- /dev/null +++ b/src/main/java/com/contentstack/cms/core/RetryCondition.java @@ -0,0 +1,40 @@ +package com.contentstack.cms.core; + +import org.jetbrains.annotations.Nullable; + +/** + * Interface for determining if an error should be retried. + *

+ * This interface allows custom logic to determine whether a failed request + * should be retried based on the HTTP status code and/or the exception that occurred. + *

+ *

+ * Status code conventions: + *

    + *
  • 0 = Network error (IOException, SocketTimeoutException) - typically retryable
  • + *
  • -1 = Unknown error - typically not retryable
  • + *
  • Other values = HTTP status codes (200-599)
  • + *
+ *

+ * + * @author Contentstack + * @version v1.0.0 + * @since 2026-01-28 + */ +@FunctionalInterface +public interface RetryCondition { + + /** + * Determines if an error should be retried. + * + * @param statusCode HTTP status code from the response, or: + *
    + *
  • 0 for network errors (IOException, SocketTimeoutException)
  • + *
  • -1 for unknown errors
  • + *
+ * @param error The throwable that caused the failure. May be null if statusCode + * is available from the response. + * @return true if the error should be retried, false otherwise + */ + boolean shouldRetry(int statusCode, @Nullable Throwable error); +} diff --git a/src/main/java/com/contentstack/cms/core/RetryConfig.java b/src/main/java/com/contentstack/cms/core/RetryConfig.java new file mode 100644 index 00000000..303c125f --- /dev/null +++ b/src/main/java/com/contentstack/cms/core/RetryConfig.java @@ -0,0 +1,216 @@ +package com.contentstack.cms.core; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Configuration for retry behavior across the SDK. + *

+ * This class centralizes retry configuration similar to the JavaScript Delivery SDK's + * {@code fetchOptions.retry} parameters. It controls: + *

    + *
  • How many times to retry (retryLimit)
  • + *
  • How long to wait between retries (retryDelay, retryDelayOptions)
  • + *
  • Which errors should be retried (retryCondition)
  • + *
+ *

+ *

+ * This configuration is used by: + *

    + *
  • AuthInterceptor (non-OAuth synchronous calls)
  • + *
  • OAuthInterceptor (OAuth synchronous calls)
  • + *
  • RetryCallback (asynchronous calls)
  • + *
+ *

+ * + * @author Contentstack + * @version v1.0.0 + * @since 2026-01-28 + */ +public class RetryConfig { + + /** + * Default retry limit (matches current SDK behavior for backward compatibility). + * Can be changed to 5 to match JS SDK default. + */ + private static final int DEFAULT_RETRY_LIMIT = 3; + + /** + * Default retry delay in milliseconds (fallback when no delay options specified). + */ + private static final long DEFAULT_RETRY_DELAY = 300; + + /** + * Maximum number of retry attempts before giving up. + */ + private final int retryLimit; + + /** + * Fallback delay in milliseconds (used when retryDelayOptions are not configured). + */ + private final long retryDelay; + + /** + * Condition function to determine if an error should be retried. + */ + @NotNull + private final RetryCondition retryCondition; + + /** + * Options for calculating retry delays (base, custom backoff). + */ + @Nullable + private final RetryDelayOptions retryDelayOptions; + + /** + * Private constructor. Use Builder to create instances. + */ + private RetryConfig(Builder builder) { + this.retryLimit = builder.retryLimit; + this.retryDelay = builder.retryDelay; + this.retryCondition = builder.retryCondition != null + ? builder.retryCondition + : DefaultRetryCondition.getInstance(); + this.retryDelayOptions = builder.retryDelayOptions; + } + + /** + * Gets the maximum number of retry attempts. + * + * @return the retry limit + */ + public int getRetryLimit() { + return retryLimit; + } + + /** + * Gets the fallback delay in milliseconds. + * + * @return the retry delay + */ + public long getRetryDelay() { + return retryDelay; + } + + /** + * Gets the retry condition function. + * + * @return the retry condition (never null) + */ + @NotNull + public RetryCondition getRetryCondition() { + return retryCondition; + } + + /** + * Gets the retry delay options. + * + * @return the retry delay options, or null if not set + */ + @Nullable + public RetryDelayOptions getRetryDelayOptions() { + return retryDelayOptions; + } + + /** + * Creates a default RetryConfig with sensible defaults. + *

+ * Default values: + *

    + *
  • retryLimit: 3 (for backward compatibility with current SDK)
  • + *
  • retryDelay: 300ms
  • + *
  • retryCondition: DefaultRetryCondition (retries on 408, 429, 5xx, network errors)
  • + *
  • retryDelayOptions: null (uses fixed retryDelay)
  • + *
+ *

+ * + * @return a default RetryConfig instance + */ + public static RetryConfig defaultConfig() { + return new Builder().build(); + } + + /** + * Builder for creating RetryConfig instances. + */ + public static class Builder { + private int retryLimit = DEFAULT_RETRY_LIMIT; + private long retryDelay = DEFAULT_RETRY_DELAY; + private RetryCondition retryCondition; + private RetryDelayOptions retryDelayOptions; + + /** + * Sets the maximum number of retry attempts. + * Default: 3 (for backward compatibility) + * + * @param retryLimit the retry limit (must be >= 0) + * @return this builder instance + * @throws IllegalArgumentException if retryLimit is negative + */ + public Builder retryLimit(int retryLimit) { + if (retryLimit < 0) { + throw new IllegalArgumentException("Retry limit must be >= 0"); + } + this.retryLimit = retryLimit; + return this; + } + + /** + * Sets the fallback delay in milliseconds. + * This is used when retryDelayOptions are not configured. + * Default: 300ms + * + * @param retryDelay the delay in milliseconds (must be >= 0) + * @return this builder instance + * @throws IllegalArgumentException if retryDelay is negative + */ + public Builder retryDelay(long retryDelay) { + if (retryDelay < 0) { + throw new IllegalArgumentException("Retry delay must be >= 0"); + } + this.retryDelay = retryDelay; + return this; + } + + /** + * Sets a custom retry condition function. + * If not set, DefaultRetryCondition will be used. + * + * @param retryCondition the retry condition function + * @return this builder instance + */ + public Builder retryCondition(@NotNull RetryCondition retryCondition) { + this.retryCondition = retryCondition; + return this; + } + + /** + * Sets the retry delay options (base, custom backoff). + * + * @param retryDelayOptions the delay options + * @return this builder instance + */ + public Builder retryDelayOptions(@Nullable RetryDelayOptions retryDelayOptions) { + this.retryDelayOptions = retryDelayOptions; + return this; + } + + /** + * Builds the RetryConfig instance. + * + * @return a new RetryConfig instance + */ + public RetryConfig build() { + return new RetryConfig(this); + } + } + + /** + * Creates a new builder for RetryConfig. + * + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); + } +} diff --git a/src/main/java/com/contentstack/cms/core/RetryDelayOptions.java b/src/main/java/com/contentstack/cms/core/RetryDelayOptions.java new file mode 100644 index 00000000..eccd8ecb --- /dev/null +++ b/src/main/java/com/contentstack/cms/core/RetryDelayOptions.java @@ -0,0 +1,112 @@ +package com.contentstack.cms.core; + +import org.jetbrains.annotations.Nullable; + +/** + * Configuration options for retry delay calculation. + *

+ * Supports three delay strategies (in order of precedence): + *

    + *
  1. Custom Backoff: If {@code customBackoff} is set, it will be used
  2. + *
  3. Linear Backoff: If {@code base} is set, delay = base * retryCount
  4. + *
  5. Fixed Delay: Falls back to {@code retryDelay} from RetryConfig
  6. + *
+ *

+ * + * @author Contentstack + * @version v1.0.0 + * @since 2026-01-28 + */ +public class RetryDelayOptions { + + /** + * Base multiplier for linear backoff. + * Delay becomes: base * retryCount + * Example: base = 1000, retryCount = 1 → 1000ms, retryCount = 2 → 2000ms + */ + private final Long base; + + /** + * Custom backoff function for advanced delay calculation. + * Takes precedence over base if both are set. + */ + private final CustomBackoff customBackoff; + + /** + * Private constructor. Use Builder to create instances. + */ + private RetryDelayOptions(Builder builder) { + this.base = builder.base; + this.customBackoff = builder.customBackoff; + } + + /** + * Gets the base multiplier for linear backoff. + * + * @return the base multiplier, or null if not set + */ + @Nullable + public Long getBase() { + return base; + } + + /** + * Gets the custom backoff function. + * + * @return the custom backoff function, or null if not set + */ + @Nullable + public CustomBackoff getCustomBackoff() { + return customBackoff; + } + + /** + * Builder for creating RetryDelayOptions instances. + */ + public static class Builder { + private Long base; + private CustomBackoff customBackoff; + + /** + * Sets the base multiplier for linear backoff. + * Delay calculation: base * retryCount + * + * @param base the base multiplier in milliseconds + * @return this builder instance + */ + public Builder base(long base) { + this.base = base; + return this; + } + + /** + * Sets a custom backoff function for advanced delay calculation. + * This takes precedence over base if both are set. + * + * @param customBackoff the custom backoff function + * @return this builder instance + */ + public Builder customBackoff(CustomBackoff customBackoff) { + this.customBackoff = customBackoff; + return this; + } + + /** + * Builds the RetryDelayOptions instance. + * + * @return a new RetryDelayOptions instance + */ + public RetryDelayOptions build() { + return new RetryDelayOptions(this); + } + } + + /** + * Creates a new builder for RetryDelayOptions. + * + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); + } +} diff --git a/src/main/java/com/contentstack/cms/core/RetryUtil.java b/src/main/java/com/contentstack/cms/core/RetryUtil.java new file mode 100644 index 00000000..c1fe8844 --- /dev/null +++ b/src/main/java/com/contentstack/cms/core/RetryUtil.java @@ -0,0 +1,81 @@ +package com.contentstack.cms.core; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Utility class for retry-related calculations. + *

+ * Provides helper methods for calculating retry delays based on RetryConfig. + *

+ * + * @author Contentstack + * @version v1.0.0 + * @since 2026-01-28 + */ +public class RetryUtil { + + /** + * Private constructor to prevent instantiation. + */ + private RetryUtil() { + throw new AssertionError("RetryUtil should not be instantiated"); + } + + /** + * Calculates the delay in milliseconds before the next retry attempt. + *

+ * Priority order: + *

    + *
  1. If {@code retryDelayOptions.customBackoff} is set, use it
  2. + *
  3. If {@code retryDelayOptions.base} is set, use linear backoff: base * retryCount
  4. + *
  5. Otherwise, use fixed {@code retryDelay} from RetryConfig
  6. + *
+ *

+ * + * @param config the RetryConfig containing delay settings + * @param retryCount the current retry attempt number (1-based: 1st retry, 2nd retry, etc.) + * @param statusCode HTTP status code from the response, or: + *
    + *
  • 0 for network errors
  • + *
  • -1 for unknown errors
  • + *
+ * @param error the throwable that caused the failure (may be null) + * @return the delay in milliseconds before the next retry + * @throws IllegalArgumentException if config is null + */ + public static long calculateDelay(@NotNull RetryConfig config, int retryCount, int statusCode, + @Nullable Throwable error) { + if (config == null) { + throw new IllegalArgumentException("RetryConfig cannot be null"); + } + + RetryDelayOptions delayOptions = config.getRetryDelayOptions(); + + // Priority 1: Custom backoff function + if (delayOptions != null && delayOptions.getCustomBackoff() != null) { + return delayOptions.getCustomBackoff().calculate(retryCount, statusCode, error); + } + + // Priority 2: Linear backoff (base * retryCount) + if (delayOptions != null && delayOptions.getBase() != null && delayOptions.getBase() > 0) { + return delayOptions.getBase() * retryCount; + } + + // Priority 3: Fixed delay (fallback) + return config.getRetryDelay(); + } + + /** + * Calculates the delay in milliseconds before the next retry attempt. + * Convenience method that passes null for the error parameter. + * + * @param config the RetryConfig containing delay settings + * @param retryCount the current retry attempt number (1-based) + * @param statusCode HTTP status code from the response + * @return the delay in milliseconds before the next retry + */ + public static long calculateDelay(@NotNull RetryConfig config, int retryCount, int statusCode) { + return calculateDelay(config, retryCount, statusCode, null); + } +} diff --git a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java index dcae7694..56f110a9 100644 --- a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java +++ b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java @@ -6,17 +6,18 @@ import java.util.concurrent.TimeoutException; import com.contentstack.cms.core.Util; - +import com.contentstack.cms.core.RetryConfig; +import com.contentstack.cms.core.RetryUtil; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; public class OAuthInterceptor implements Interceptor { - private static final int MAX_RETRIES = 3; private final OAuthHandler oauthHandler; private String[] earlyAccess; private final Object refreshLock = new Object(); + private RetryConfig retryConfig = RetryConfig.defaultConfig(); public OAuthInterceptor(OAuthHandler oauthHandler) { this.oauthHandler = oauthHandler; @@ -114,7 +115,7 @@ private Response executeRequest(Chain chain, Request request, int retryCount) th Response response = chain.proceed(request); // Handle error responses - if (!response.isSuccessful() && retryCount < MAX_RETRIES) { + if (!response.isSuccessful() && retryCount < retryConfig.getRetryLimit()) { int code = response.code(); response.close(); @@ -140,9 +141,9 @@ private Response executeRequest(Chain chain, Request request, int retryCount) th } // Handle other retryable errors (429, 5xx) - if ((code == 429 || code >= 500) && code != 501) { + if (retryConfig.getRetryCondition().shouldRetry(code, null)) { try { - long delay = Math.min(1000 * (1 << retryCount), 30000); + long delay = RetryUtil.calculateDelay(retryConfig, retryCount+1, code); Thread.sleep(delay); return executeRequest(chain, request, retryCount + 1); } catch (InterruptedException e) { @@ -154,4 +155,8 @@ private Response executeRequest(Chain chain, Request request, int retryCount) th return response; } + + public void setRetryConfig(RetryConfig retryConfig) { + this.retryConfig = retryConfig != null ? retryConfig : RetryConfig.defaultConfig(); + } } diff --git a/src/test/java/com/contentstack/cms/core/AuthInterceptorTest.java b/src/test/java/com/contentstack/cms/core/AuthInterceptorTest.java index 029d4c15..310f964d 100644 --- a/src/test/java/com/contentstack/cms/core/AuthInterceptorTest.java +++ b/src/test/java/com/contentstack/cms/core/AuthInterceptorTest.java @@ -3,6 +3,7 @@ import okhttp3.*; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -16,6 +17,155 @@ public void setup() { authInterceptor = new AuthInterceptor("test-authtoken"); } + // --- Retry tests (RetryConfig) --- + + @Test + @Tag("unit") + public void testRetryConfig_setRetryConfig() { + RetryConfig config = RetryConfig.builder().retryLimit(5).build(); + authInterceptor.setRetryConfig(config); + Assertions.assertNotNull(authInterceptor.retryConfig); + Assertions.assertEquals(5, authInterceptor.retryConfig.getRetryLimit()); + } + + @Test + @Tag("unit") + public void testRetry_on429_thenSuccess_retriesAndReturnsSuccess() throws IOException { + authInterceptor.setRetryConfig(RetryConfig.builder().retryLimit(3).retryDelay(10).build()); + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/user") + .get() + .build(); + RetryTestChain chain = new RetryTestChain(request, 429, 200); + try (Response response = authInterceptor.intercept(chain)) { + Assertions.assertEquals(200, response.code()); + Assertions.assertEquals(2, chain.getProceedCount()); + } + } + + @Test + @Tag("unit") + public void testRetry_on503_thenSuccess_retriesAndReturnsSuccess() throws IOException { + authInterceptor.setRetryConfig(RetryConfig.builder().retryLimit(3).retryDelay(10).build()); + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/user") + .get() + .build(); + RetryTestChain chain = new RetryTestChain(request, 503, 200); + try (Response response = authInterceptor.intercept(chain)) { + Assertions.assertEquals(200, response.code()); + Assertions.assertEquals(2, chain.getProceedCount()); + } + } + + @Test + @Tag("unit") + public void testRetry_on404_doesNotRetry() throws IOException { + authInterceptor.setRetryConfig(RetryConfig.builder().retryLimit(3).retryDelay(10).build()); + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/user") + .get() + .build(); + RetryTestChain chain = new RetryTestChain(request, 404, 200); + try (Response response = authInterceptor.intercept(chain)) { + Assertions.assertEquals(404, response.code()); + Assertions.assertEquals(1, chain.getProceedCount()); + } + } + + @Test + @Tag("unit") + public void testRetry_on200_doesNotRetry() throws IOException { + authInterceptor.setRetryConfig(RetryConfig.builder().retryLimit(3).retryDelay(10).build()); + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/user") + .get() + .build(); + RetryTestChain chain = new RetryTestChain(request, 200, 200); + try (Response response = authInterceptor.intercept(chain)) { + Assertions.assertEquals(200, response.code()); + Assertions.assertEquals(1, chain.getProceedCount()); + } + } + + /** + * Chain that returns a configurable response code on first proceed, then successCode on subsequent calls. + */ + private static class RetryTestChain implements Interceptor.Chain { + private final Request originalRequest; + private final int firstResponseCode; + private final int successCode; + private int proceedCount = 0; + + RetryTestChain(Request request, int firstResponseCode, int successCode) { + this.originalRequest = request; + this.firstResponseCode = firstResponseCode; + this.successCode = successCode; + } + + int getProceedCount() { + return proceedCount; + } + + @Override + public Request request() { + return originalRequest; + } + + @Override + public Response proceed(Request request) throws IOException { + proceedCount++; + int code = proceedCount == 1 ? firstResponseCode : successCode; + return new Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(code) + .message(code == 200 ? "OK" : "Error") + .body(ResponseBody.create("{}", MediaType.parse("application/json"))) + .build(); + } + + @Override + public Connection connection() { + return null; + } + + @Override + public int connectTimeoutMillis() { + return 0; + } + + @Override + public Interceptor.Chain withConnectTimeout(int timeout, java.util.concurrent.TimeUnit unit) { + return this; + } + + @Override + public int readTimeoutMillis() { + return 0; + } + + @Override + public Interceptor.Chain withReadTimeout(int timeout, java.util.concurrent.TimeUnit unit) { + return this; + } + + @Override + public int writeTimeoutMillis() { + return 0; + } + + @Override + public Interceptor.Chain withWriteTimeout(int timeout, java.util.concurrent.TimeUnit unit) { + return this; + } + + @Override + public Call call() { + return null; + } + } + @Test public void AuthInterceptor() { AuthInterceptor expected = new AuthInterceptor("abc"); @@ -35,7 +185,7 @@ public void testSetAuthtoken() { public void testBadArgumentException() { BadArgumentException exception = new BadArgumentException("Invalid Argument"); String message = exception.getLocalizedMessage(); - Assertions.assertEquals("Invalid Argument", message.toString()); + Assertions.assertEquals("Invalid Argument", message); } @Test diff --git a/src/test/java/com/contentstack/cms/core/DefaultRetryConditionTest.java b/src/test/java/com/contentstack/cms/core/DefaultRetryConditionTest.java new file mode 100644 index 00000000..096ae2d8 --- /dev/null +++ b/src/test/java/com/contentstack/cms/core/DefaultRetryConditionTest.java @@ -0,0 +1,91 @@ +package com.contentstack.cms.core; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.SocketTimeoutException; + +@Tag("unit") +class DefaultRetryConditionTest { + + private final RetryCondition condition = DefaultRetryCondition.getInstance(); + + @Test + void getInstance_returnsSingleton() { + RetryCondition a = DefaultRetryCondition.getInstance(); + RetryCondition b = DefaultRetryCondition.getInstance(); + Assertions.assertSame(a, b); + } + + @Test + void shouldRetry_status408_returnsTrue() { + Assertions.assertTrue(condition.shouldRetry(408, null)); + } + + @Test + void shouldRetry_status429_returnsTrue() { + Assertions.assertTrue(condition.shouldRetry(429, null)); + } + + @Test + void shouldRetry_status500_returnsTrue() { + Assertions.assertTrue(condition.shouldRetry(500, null)); + } + + @Test + void shouldRetry_status502_returnsTrue() { + Assertions.assertTrue(condition.shouldRetry(502, null)); + } + + @Test + void shouldRetry_status503_returnsTrue() { + Assertions.assertTrue(condition.shouldRetry(503, null)); + } + + @Test + void shouldRetry_status504_returnsTrue() { + Assertions.assertTrue(condition.shouldRetry(504, null)); + } + + @Test + void shouldRetry_status404_returnsFalse() { + Assertions.assertFalse(condition.shouldRetry(404, null)); + } + + @Test + void shouldRetry_status400_returnsFalse() { + Assertions.assertFalse(condition.shouldRetry(400, null)); + } + + @Test + void shouldRetry_status200_returnsFalse() { + Assertions.assertFalse(condition.shouldRetry(200, null)); + } + + @Test + void shouldRetry_status0_networkError_returnsTrue() { + Assertions.assertTrue(condition.shouldRetry(0, null)); + } + + @Test + void shouldRetry_statusMinus1_unknownNoError_returnsFalse() { + Assertions.assertFalse(condition.shouldRetry(-1, null)); + } + + @Test + void shouldRetry_statusMinus1_withIOException_returnsTrue() { + Assertions.assertTrue(condition.shouldRetry(-1, new IOException("network"))); + } + + @Test + void shouldRetry_statusMinus1_withSocketTimeoutException_returnsTrue() { + Assertions.assertTrue(condition.shouldRetry(-1, new SocketTimeoutException("timeout"))); + } + + @Test + void shouldRetry_statusMinus1_withOtherException_returnsFalse() { + Assertions.assertFalse(condition.shouldRetry(-1, new RuntimeException("other"))); + } +} diff --git a/src/test/java/com/contentstack/cms/core/RetryConfigTest.java b/src/test/java/com/contentstack/cms/core/RetryConfigTest.java new file mode 100644 index 00000000..13632686 --- /dev/null +++ b/src/test/java/com/contentstack/cms/core/RetryConfigTest.java @@ -0,0 +1,91 @@ +package com.contentstack.cms.core; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +class RetryConfigTest { + + @Test + void defaultConfig_returnsNonNull() { + RetryConfig config = RetryConfig.defaultConfig(); + Assertions.assertNotNull(config); + } + + @Test + void defaultConfig_hasExpectedDefaults() { + RetryConfig config = RetryConfig.defaultConfig(); + Assertions.assertEquals(3, config.getRetryLimit()); + Assertions.assertEquals(300L, config.getRetryDelay()); + Assertions.assertNotNull(config.getRetryCondition()); + Assertions.assertTrue(config.getRetryCondition() instanceof DefaultRetryCondition); + } + + @Test + void builder_withCustomLimit() { + RetryConfig config = RetryConfig.builder() + .retryLimit(5) + .build(); + Assertions.assertEquals(5, config.getRetryLimit()); + Assertions.assertEquals(300L, config.getRetryDelay()); + } + + @Test + void builder_withCustomDelay() { + RetryConfig config = RetryConfig.builder() + .retryDelay(500) + .build(); + Assertions.assertEquals(3, config.getRetryLimit()); + Assertions.assertEquals(500L, config.getRetryDelay()); + } + + @Test + void builder_withCustomCondition() { + RetryCondition customCondition = (statusCode, error) -> statusCode == 503; + RetryConfig config = RetryConfig.builder() + .retryCondition(customCondition) + .build(); + Assertions.assertSame(customCondition, config.getRetryCondition()); + } + + @Test + void builder_withNullCondition_usesDefault() { + RetryConfig config = RetryConfig.builder().build(); + Assertions.assertNotNull(config.getRetryCondition()); + Assertions.assertTrue(config.getRetryCondition() instanceof DefaultRetryCondition); + } + + @Test + void builder_withRetryDelayOptions() { + RetryDelayOptions options = RetryDelayOptions.builder().base(1000).build(); + RetryConfig config = RetryConfig.builder() + .retryDelayOptions(options) + .build(); + RetryDelayOptions delayOptions = config.getRetryDelayOptions(); + Assertions.assertNotNull(delayOptions); + Long base = delayOptions.getBase(); + Assertions.assertNotNull(base); + Assertions.assertEquals(1000L, base.longValue()); + } + + @Test + void builder_retryLimitNegative_throws() { + IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> + RetryConfig.builder().retryLimit(-1).build()); + Assertions.assertNotNull(ex.getMessage()); + } + + @Test + void builder_retryDelayNegative_throws() { + IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> + RetryConfig.builder().retryDelay(-1).build()); + Assertions.assertNotNull(ex.getMessage()); + } + + @Test + void builder_retryLimitZero_allowed() { + RetryConfig config = RetryConfig.builder().retryLimit(0).build(); + Assertions.assertEquals(0, config.getRetryLimit()); + } +} diff --git a/src/test/java/com/contentstack/cms/core/RetryTest.java b/src/test/java/com/contentstack/cms/core/RetryTest.java index 95c68bff..091504b1 100644 --- a/src/test/java/com/contentstack/cms/core/RetryTest.java +++ b/src/test/java/com/contentstack/cms/core/RetryTest.java @@ -8,18 +8,20 @@ import retrofit2.Call; import retrofit2.Response; +import java.util.concurrent.atomic.AtomicBoolean; + @Tag("unit") class RetryTest { @Test - void testRetryPolicy() { + void testRetryPolicy_backwardCompatibleConstructor() { Contentstack client = new Contentstack.Builder() .setAuthtoken("fake@authtoken") .build(); Call userModelCall = client.user().getUser(); - RetryCallback callback = new RetryCallback(userModelCall) { + RetryCallback callback = new RetryCallback(userModelCall) { @Override - public void onResponse(Call call, Response response) { + public void onResponse(Call call, Response response) { Assertions.assertNotNull(call); Assertions.assertNotNull(response); } @@ -28,4 +30,90 @@ public void onResponse(Call call, Response response) { Throwable throwable = new Throwable(); callback.onFailure(userModelCall, throwable); } -} \ No newline at end of file + + @Test + void testRetryCallback_withRetryConfig() { + Contentstack client = new Contentstack.Builder() + .setAuthtoken("fake@authtoken") + .build(); + Call call = client.user().getUser(); + RetryConfig config = RetryConfig.builder().retryLimit(2).build(); + RetryCallback callback = new RetryCallback(call, config) { + @Override + public void onResponse(Call c, Response response) { + Assertions.fail("Unexpected success"); + } + }; + callback.onFailure(call, new Throwable("fail")); + } + + @Test + void testOnFinalFailure_calledWhenErrorNotRetryable() { + Contentstack client = new Contentstack.Builder() + .setAuthtoken("fake@authtoken") + .build(); + Call call = client.user().getUser(); + RetryConfig noRetryConfig = RetryConfig.builder() + .retryCondition((statusCode, error) -> false) + .retryLimit(3) + .build(); + AtomicBoolean finalFailureCalled = new AtomicBoolean(false); + RetryCallback callback = new RetryCallback(call, noRetryConfig) { + @Override + public void onResponse(Call c, Response response) { + Assertions.fail("Unexpected success"); + } + + @Override + protected void onFinalFailure(Call c, Throwable t) { + finalFailureCalled.set(true); + } + }; + callback.onFailure(call, new RuntimeException("not retryable")); + Assertions.assertTrue(finalFailureCalled.get(), "onFinalFailure should be called when error is not retryable"); + } + + @Test + void testOnFinalFailure_calledWhenRetryLimitReached() { + Contentstack client = new Contentstack.Builder() + .setAuthtoken("fake@authtoken") + .build(); + Call call = client.user().getUser(); + RetryConfig limitZeroConfig = RetryConfig.builder() + .retryLimit(0) + .retryCondition((statusCode, error) -> true) + .build(); + AtomicBoolean finalFailureCalled = new AtomicBoolean(false); + RetryCallback callback = new RetryCallback(call, limitZeroConfig) { + @Override + public void onResponse(Call c, Response response) { + Assertions.fail("Unexpected success"); + } + + @Override + protected void onFinalFailure(Call c, Throwable t) { + finalFailureCalled.set(true); + } + }; + callback.onFailure(call, new java.io.IOException("network error")); + Assertions.assertTrue(finalFailureCalled.get(), "onFinalFailure should be called when retry limit is 0"); + } + + @Test + void testRetryCallback_withContentstackGetRetryConfig() { + Contentstack client = new Contentstack.Builder() + .setAuthtoken("fake@authtoken") + .setRetryConfig(RetryConfig.builder().retryLimit(5).build()) + .build(); + Assertions.assertNotNull(client.getRetryConfig()); + Assertions.assertEquals(5, client.getRetryConfig().getRetryLimit()); + Call call = client.user().getUser(); + RetryCallback callback = new RetryCallback(call, client.getRetryConfig()) { + @Override + public void onResponse(Call c, Response response) { + Assertions.assertNotNull(response); + } + }; + callback.onFailure(call, new Throwable("test")); + } +} diff --git a/src/test/java/com/contentstack/cms/core/RetryUtilTest.java b/src/test/java/com/contentstack/cms/core/RetryUtilTest.java new file mode 100644 index 00000000..2dd89f93 --- /dev/null +++ b/src/test/java/com/contentstack/cms/core/RetryUtilTest.java @@ -0,0 +1,70 @@ +package com.contentstack.cms.core; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("unit") +class RetryUtilTest { + + @Test + void calculateDelay_nullConfig_throws() { + IllegalArgumentException ex = Assertions.assertThrows(IllegalArgumentException.class, () -> + RetryUtil.calculateDelay(null, 1, 429)); + Assertions.assertNotNull(ex.getMessage()); + } + + @Test + void calculateDelay_defaultConfig_returnsFixedDelay() { + RetryConfig config = RetryConfig.defaultConfig(); + long delay = RetryUtil.calculateDelay(config, 1, 429); + Assertions.assertEquals(300L, delay); + } + + @Test + void calculateDelay_customRetryDelay_returnsThatDelay() { + RetryConfig config = RetryConfig.builder().retryDelay(500).build(); + long delay = RetryUtil.calculateDelay(config, 1, 429); + Assertions.assertEquals(500L, delay); + } + + @Test + void calculateDelay_withBase_returnsLinearBackoff() { + RetryConfig config = RetryConfig.builder() + .retryDelayOptions(RetryDelayOptions.builder().base(1000).build()) + .build(); + Assertions.assertEquals(1000L, RetryUtil.calculateDelay(config, 1, 429)); + Assertions.assertEquals(2000L, RetryUtil.calculateDelay(config, 2, 429)); + Assertions.assertEquals(3000L, RetryUtil.calculateDelay(config, 3, 429)); + } + + @Test + void calculateDelay_withCustomBackoff_returnsCustomValue() { + RetryConfig config = RetryConfig.builder() + .retryDelayOptions(RetryDelayOptions.builder() + .customBackoff((retryCount, statusCode, error) -> retryCount * 500L) + .build()) + .build(); + Assertions.assertEquals(500L, RetryUtil.calculateDelay(config, 1, 429)); + Assertions.assertEquals(1000L, RetryUtil.calculateDelay(config, 2, 503)); + } + + @Test + void calculateDelay_customBackoffTakesPrecedenceOverBase() { + RetryConfig config = RetryConfig.builder() + .retryDelayOptions(RetryDelayOptions.builder() + .base(100) + .customBackoff((retryCount, statusCode, error) -> 999L) + .build()) + .build(); + long delay = RetryUtil.calculateDelay(config, 2, 429); + Assertions.assertEquals(999L, delay); + } + + @Test + void calculateDelay_threeArgVersion_passesNullError() { + RetryConfig config = RetryConfig.defaultConfig(); + long delay = RetryUtil.calculateDelay(config, 1, 429); + Assertions.assertEquals(300L, delay); + } +} From 0616eb5030ba231fa22008b856bfe3ddae024925 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 2 Feb 2026 17:21:08 +0530 Subject: [PATCH 176/197] Remove author and version comments from Retry classes to streamline code documentation. --- .../java/com/contentstack/cms/core/RetryCallback.java | 1 - .../java/com/contentstack/cms/core/RetryCondition.java | 5 ----- .../java/com/contentstack/cms/core/RetryConfig.java | 10 ++-------- .../com/contentstack/cms/core/RetryDelayOptions.java | 4 ---- src/main/java/com/contentstack/cms/core/RetryUtil.java | 5 ----- 5 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/contentstack/cms/core/RetryCallback.java b/src/main/java/com/contentstack/cms/core/RetryCallback.java index 8fc44ef8..2fa6fcb4 100644 --- a/src/main/java/com/contentstack/cms/core/RetryCallback.java +++ b/src/main/java/com/contentstack/cms/core/RetryCallback.java @@ -16,7 +16,6 @@ /** * The Contentstack RetryCallback * - * @author ***REMOVED ** @version v0.1.0 * @since 2022-10-20 */ public abstract class RetryCallback implements Callback { diff --git a/src/main/java/com/contentstack/cms/core/RetryCondition.java b/src/main/java/com/contentstack/cms/core/RetryCondition.java index 8436bf75..3f3aa2cd 100644 --- a/src/main/java/com/contentstack/cms/core/RetryCondition.java +++ b/src/main/java/com/contentstack/cms/core/RetryCondition.java @@ -7,7 +7,6 @@ *

* This interface allows custom logic to determine whether a failed request * should be retried based on the HTTP status code and/or the exception that occurred. - *

*

* Status code conventions: *

    @@ -15,11 +14,7 @@ *
  • -1 = Unknown error - typically not retryable
  • *
  • Other values = HTTP status codes (200-599)
  • *
- *

* - * @author Contentstack - * @version v1.0.0 - * @since 2026-01-28 */ @FunctionalInterface public interface RetryCondition { diff --git a/src/main/java/com/contentstack/cms/core/RetryConfig.java b/src/main/java/com/contentstack/cms/core/RetryConfig.java index 303c125f..68b8d40b 100644 --- a/src/main/java/com/contentstack/cms/core/RetryConfig.java +++ b/src/main/java/com/contentstack/cms/core/RetryConfig.java @@ -13,7 +13,6 @@ *
  • How long to wait between retries (retryDelay, retryDelayOptions)
  • *
  • Which errors should be retried (retryCondition)
  • * - *

    *

    * This configuration is used by: *

      @@ -21,11 +20,7 @@ *
    • OAuthInterceptor (OAuth synchronous calls)
    • *
    • RetryCallback (asynchronous calls)
    • *
    - *

    * - * @author Contentstack - * @version v1.0.0 - * @since 2026-01-28 */ public class RetryConfig { @@ -122,7 +117,6 @@ public RetryDelayOptions getRetryDelayOptions() { *
  • retryCondition: DefaultRetryCondition (retries on 408, 429, 5xx, network errors)
  • *
  • retryDelayOptions: null (uses fixed retryDelay)
  • * - *

    * * @return a default RetryConfig instance */ @@ -143,7 +137,7 @@ public static class Builder { * Sets the maximum number of retry attempts. * Default: 3 (for backward compatibility) * - * @param retryLimit the retry limit (must be >= 0) + * @param retryLimit the retry limit (must be >= 0) * @return this builder instance * @throws IllegalArgumentException if retryLimit is negative */ @@ -160,7 +154,7 @@ public Builder retryLimit(int retryLimit) { * This is used when retryDelayOptions are not configured. * Default: 300ms * - * @param retryDelay the delay in milliseconds (must be >= 0) + * @param retryDelay the delay in milliseconds (must be >= 0) * @return this builder instance * @throws IllegalArgumentException if retryDelay is negative */ diff --git a/src/main/java/com/contentstack/cms/core/RetryDelayOptions.java b/src/main/java/com/contentstack/cms/core/RetryDelayOptions.java index eccd8ecb..7676d9f8 100644 --- a/src/main/java/com/contentstack/cms/core/RetryDelayOptions.java +++ b/src/main/java/com/contentstack/cms/core/RetryDelayOptions.java @@ -11,11 +11,7 @@ *
  • Linear Backoff: If {@code base} is set, delay = base * retryCount
  • *
  • Fixed Delay: Falls back to {@code retryDelay} from RetryConfig
  • * - *

    * - * @author Contentstack - * @version v1.0.0 - * @since 2026-01-28 */ public class RetryDelayOptions { diff --git a/src/main/java/com/contentstack/cms/core/RetryUtil.java b/src/main/java/com/contentstack/cms/core/RetryUtil.java index c1fe8844..229cfcb7 100644 --- a/src/main/java/com/contentstack/cms/core/RetryUtil.java +++ b/src/main/java/com/contentstack/cms/core/RetryUtil.java @@ -9,9 +9,6 @@ * Provides helper methods for calculating retry delays based on RetryConfig. *

    * - * @author Contentstack - * @version v1.0.0 - * @since 2026-01-28 */ public class RetryUtil { @@ -24,14 +21,12 @@ private RetryUtil() { /** * Calculates the delay in milliseconds before the next retry attempt. - *

    * Priority order: *

      *
    1. If {@code retryDelayOptions.customBackoff} is set, use it
    2. *
    3. If {@code retryDelayOptions.base} is set, use linear backoff: base * retryCount
    4. *
    5. Otherwise, use fixed {@code retryDelay} from RetryConfig
    6. *
    - *

    * * @param config the RetryConfig containing delay settings * @param retryCount the current retry attempt number (1-based: 1st retry, 2nd retry, etc.) From efc3b3cc90f632e4010737c76636163d7ad1238c Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 3 Feb 2026 13:12:43 +0530 Subject: [PATCH 177/197] version bump --- changelog.md | 6 ++++++ pom.xml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 5eda11b0..c98e8d77 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## v1.11.0 + +### Feb 09, 2026 + +- Enhancement: Retry Mechanism + ## v1.10.2 ### Jan 27, 2026 diff --git a/pom.xml b/pom.xml index 7cd0899e..b9788790 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.10.2 + 1.11.0 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach From a85f087507cc314e331f9f63c6a580e24ea982ea Mon Sep 17 00:00:00 2001 From: dhaval Date: Tue, 3 Mar 2026 13:14:39 +0530 Subject: [PATCH 178/197] Update sca-scan.yml --- .github/workflows/sca-scan.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/sca-scan.yml b/.github/workflows/sca-scan.yml index 2296ece3..fc88856b 100644 --- a/.github/workflows/sca-scan.yml +++ b/.github/workflows/sca-scan.yml @@ -13,3 +13,6 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: args: --fail-on=all + json: true + continue-on-error: true + - uses: contentstack/sca-policy@main From 22f2314aa65d6a4a8bf41be8318fe8da15b347e7 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 27 Mar 2026 14:31:56 +0530 Subject: [PATCH 179/197] Enhance timeout configuration with individual connect, read, and write timeouts; update Maven Surefire plugin for JUnit 5 support. --- changelog.md | 7 +++ pom.xml | 17 +++-- .../com/contentstack/cms/Contentstack.java | 37 ++++++++++- .../cms/ContentstackUnitTest.java | 63 +++++++++++++++++++ 4 files changed, 119 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index c98e8d77..80f1373b 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## v1.11.1 + +### Apr 06, 2026 + +- Fix: `setTimeout` now applies to OkHttp connect, read, and write timeouts (previously only connect). Optional `setConnectTimeout`, `setReadTimeout`, and `setWriteTimeout` override individual phases. +- Build: Maven Surefire updated with `surefire-junit-platform` so JUnit 5 tests run when enabled. + ## v1.11.0 ### Feb 09, 2026 diff --git a/pom.xml b/pom.xml index b9788790..2f2c6d43 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.11.0 + 1.11.1 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach @@ -101,6 +101,7 @@ 1.5 3.8.0 2.5.3 + 3.5.2 @@ -242,7 +243,14 @@ org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M5 + ${maven-surefire-plugin.version} + + + org.apache.maven.surefire + surefire-junit-platform + ${maven-surefire-plugin.version} + + @@ -252,8 +260,9 @@ **/*TestCase.java **/*TestSuite.java - ${project.build.directory}/surefire-reports - true + ${project.build.directory}/surefire-reports + + true true diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 1bcad23b..ebe847b3 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -599,6 +599,9 @@ public static class Builder { private String port = Util.PORT; // Default PORT for Contentstack API private String version = Util.VERSION; // Default Version for Contentstack API private int timeout = Util.TIMEOUT; // Default timeout 30 seconds + private Integer connectTimeoutSeconds; + private Integer readTimeoutSeconds; + private Integer writeTimeoutSeconds; private Boolean retry = Util.RETRY_ON_FAILURE;// Default base url for contentstack private RetryConfig retryConfig = RetryConfig.defaultConfig(); /** @@ -687,10 +690,37 @@ public Builder setVersion(@NotNull String version) { * @return Client timeout */ public Builder setTimeout(int timeout) { + validateTimeoutSeconds(timeout, "timeout"); this.timeout = timeout; return this; } + public Builder setReadTimeout(int readTimeoutSeconds) { + validateTimeoutSeconds(readTimeoutSeconds, "readTimeout"); + this.readTimeoutSeconds = readTimeoutSeconds; + return this; + } + + public Builder setWriteTimeout(int writeTimeoutSeconds) { + validateTimeoutSeconds(writeTimeoutSeconds, "writeTimeout"); + this.writeTimeoutSeconds = writeTimeoutSeconds; + return this; + } + + public Builder setConnectTimeout(int connectTimeoutSeconds) { + validateTimeoutSeconds(connectTimeoutSeconds, "connectTimeout"); + this.connectTimeoutSeconds = connectTimeoutSeconds; + return this; + } + + private static void validateTimeoutSeconds(int seconds, String name) { + if (seconds <= 0) { + throw new IllegalArgumentException(name + " must be positive."); + } + } + + + /** * Create a new connection pool with tuning parameters appropriate for a * single-user application. The tuning parameters in this pool are @@ -840,11 +870,16 @@ private void validateClient(Contentstack contentstack) { } private OkHttpClient httpClient(Contentstack contentstack, Boolean retryOnFailure) { + int connectSec = this.connectTimeoutSeconds != null ? this.connectTimeoutSeconds : this.timeout; + int readSec = this.readTimeoutSeconds != null ? this.readTimeoutSeconds : this.timeout; + int writeSec = this.writeTimeoutSeconds != null ? this.writeTimeoutSeconds : this.timeout; OkHttpClient.Builder builder = new OkHttpClient.Builder() .connectionPool(this.connectionPool) .addInterceptor(logger()) .proxy(this.proxy) - .connectTimeout(Duration.ofSeconds(this.timeout)) + .connectTimeout(Duration.ofSeconds(connectSec)) + .readTimeout(Duration.ofSeconds(readSec)) + .writeTimeout(Duration.ofSeconds(writeSec)) .retryOnConnectionFailure(retryOnFailure); // Add either OAuth or traditional auth interceptor diff --git a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java index b2666c47..9e84c9b8 100644 --- a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java +++ b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java @@ -19,6 +19,7 @@ import java.net.Proxy; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; public class ContentstackUnitTest { @@ -167,6 +168,68 @@ void setTimeout() { Assertions.assertEquals(3, contentstack.timeout); } + @Test + void setTimeoutZeroThrows() { + Assertions.assertThrows(IllegalArgumentException.class, + () -> new Contentstack.Builder().setTimeout(0)); + } + + @Test + void setTimeoutNegativeThrows() { + Assertions.assertThrows(IllegalArgumentException.class, + () -> new Contentstack.Builder().setTimeout(-1)); + } + + @Test + void setReadTimeoutZeroThrows() { + Assertions.assertThrows(IllegalArgumentException.class, + () -> new Contentstack.Builder().setReadTimeout(0)); + } + + @Test + void setWriteTimeoutNegativeThrows() { + Assertions.assertThrows(IllegalArgumentException.class, + () -> new Contentstack.Builder().setWriteTimeout(-5)); + } + + @Test + void setConnectTimeoutInvalidThrows() { + Assertions.assertThrows(IllegalArgumentException.class, + () -> new Contentstack.Builder().setConnectTimeout(-1)); + } + + @Test + void okHttpTimeoutsMatchSetTimeout() { + Contentstack client = new Contentstack.Builder().setTimeout(45).build(); + OkHttpClient ok = (OkHttpClient) client.instance.callFactory(); + Assertions.assertEquals(45_000, ok.connectTimeoutMillis()); + Assertions.assertEquals(45_000, ok.readTimeoutMillis()); + Assertions.assertEquals(45_000, ok.writeTimeoutMillis()); + } + + @Test + void okHttpDefaultTimeoutsUseUtilTimeout() { + Contentstack client = new Contentstack.Builder().build(); + OkHttpClient ok = (OkHttpClient) client.instance.callFactory(); + int expectedMs = (int) TimeUnit.SECONDS.toMillis(30); + Assertions.assertEquals(expectedMs, ok.connectTimeoutMillis()); + Assertions.assertEquals(expectedMs, ok.readTimeoutMillis()); + Assertions.assertEquals(expectedMs, ok.writeTimeoutMillis()); + } + + @Test + void okHttpPerLegOverridesFallBackToSetTimeout() { + Contentstack client = new Contentstack.Builder() + .setTimeout(10) + .setReadTimeout(90) + .build(); + OkHttpClient ok = (OkHttpClient) client.instance.callFactory(); + Assertions.assertEquals(10_000, ok.connectTimeoutMillis()); + Assertions.assertEquals(90_000, ok.readTimeoutMillis()); + Assertions.assertEquals(10_000, ok.writeTimeoutMillis()); + Assertions.assertEquals(10, client.timeout); + } + @Test void testSetAuthtoken() { Contentstack contentstack = new Contentstack.Builder() From a384402e3e640473e4fab291e68afa68215efd0b Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 27 Mar 2026 14:37:03 +0530 Subject: [PATCH 180/197] snyk fixes --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 2f2c6d43..a0bbd325 100644 --- a/pom.xml +++ b/pom.xml @@ -89,11 +89,11 @@ 3.0.0 5.2.2 3.1.12 - 2.12.0 - 2.12.0 - 5.1.0 + 3.0.0 + 3.0.0 + 5.3.2 0.8.13 - 1.18.38 + 1.18.42 5.11.4 5.10.1 2.13.2 From 9bf0b09191340e799143db17f4a06d6f7e7783ae Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Sat, 4 Apr 2026 19:54:43 +0530 Subject: [PATCH 181/197] Update Maven Surefire plugin version to 3.0.0-M5 and remove version property from pom.xml --- pom.xml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index a0bbd325..2feb459a 100644 --- a/pom.xml +++ b/pom.xml @@ -101,7 +101,6 @@ 1.5 3.8.0 2.5.3 - 3.5.2 @@ -243,14 +242,7 @@ org.apache.maven.plugins maven-surefire-plugin - ${maven-surefire-plugin.version} - - - org.apache.maven.surefire - surefire-junit-platform - ${maven-surefire-plugin.version} - - + 3.0.0-M5 From a711bdd82ba581e4a052ad6bb79d6fcd8dffdf8c Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 7 Apr 2026 12:41:32 +0530 Subject: [PATCH 182/197] Add agent guide and skills documentation --- .cursor/rules/README.md | 5 +++ AGENTS.md | 51 +++++++++++++++++++++++ skills/README.md | 16 +++++++ skills/code-review/SKILL.md | 42 +++++++++++++++++++ skills/contentstack-java-cma-sdk/SKILL.md | 35 ++++++++++++++++ skills/dev-workflow/SKILL.md | 41 ++++++++++++++++++ skills/http-client-stack/SKILL.md | 42 +++++++++++++++++++ skills/java/SKILL.md | 37 ++++++++++++++++ skills/testing/SKILL.md | 37 ++++++++++++++++ 9 files changed, 306 insertions(+) create mode 100644 .cursor/rules/README.md create mode 100644 AGENTS.md create mode 100644 skills/README.md create mode 100644 skills/code-review/SKILL.md create mode 100644 skills/contentstack-java-cma-sdk/SKILL.md create mode 100644 skills/dev-workflow/SKILL.md create mode 100644 skills/http-client-stack/SKILL.md create mode 100644 skills/java/SKILL.md create mode 100644 skills/testing/SKILL.md diff --git a/.cursor/rules/README.md b/.cursor/rules/README.md new file mode 100644 index 00000000..d8f35453 --- /dev/null +++ b/.cursor/rules/README.md @@ -0,0 +1,5 @@ +# Cursor (optional) + +*Cursor* users: start at *[AGENTS.md](../../AGENTS.md)*. All conventions live in **skills/*/SKILL.md**. + +This folder only points contributors to *AGENTS.md* so editor-specific config does not duplicate the canonical docs. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..d4a23aee --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,51 @@ +# Contentstack Management Java SDK – Agent guide + +*Universal entry point* for contributors and AI agents. Detailed conventions live in **skills/*/SKILL.md**. + +## What this repo is + +| Field | Detail | +| --- | --- | +| *Name:* | [contentstack-management-java](https://github.com/contentstack/contentstack-management-java) | +| *Purpose:* | Java client library for the Contentstack Content Management API (CMA): create, update, delete, and fetch account content and configuration from JVM applications. | +| *Out of scope (if any):* | Content Delivery API (CDA) and delivery-only use cases belong in the Delivery SDK, not this package. This SDK is read/write against CMA; do not treat it as the primary way to serve content to end users. | + +## Tech stack (at a glance) + +| Area | Details | +| --- | --- | +| Language | Java 8 (source/target in `pom.xml`); README suggests Java 8+ for consumers. | +| Build | Apache Maven; `pom.xml` at repo root. | +| Tests | JUnit 5 / Vintage, Mockito, OkHttp MockWebServer; tests under `src/test/java/com/contentstack/cms/`. | +| Lint / coverage | Compiler: `-Xlint:all` (see `maven-compiler-plugin`). Coverage: JaCoCo (`jacoco-maven-plugin`); HTML report under `target/site/jacoco` after tests + `jacoco:report`. No separate Checkstyle/SpotBugs in this `pom.xml`. | +| Other | HTTP: Retrofit 3, OkHttp 5, Gson; RxJava 3; Lombok for generated code. | + +## Commands (quick reference) + +| Command type | Command | +| --- | --- | +| Build | `mvn clean compile` | +| Test | `mvn clean test -DskipTests=false` | +| Lint | `mvn compile` (compiler warnings; see Tech stack) | +| Coverage | `mvn clean test -DskipTests=false jacoco:report` (open `target/site/jacoco/index.html`) | + +**Note:** `maven-surefire-plugin` sets `skipTests` to `true` by default in this repo, so you must pass `-DskipTests=false` (or override in your IDE) to run unit tests locally. + +**CI:** [.github/workflows/coverage.yml](.github/workflows/coverage.yml) runs `mvn clean package` and JaCoCo steps on push. Other workflows under [.github/workflows/](.github/workflows/) handle publish, scans, and branch checks. + +## Where the documentation lives: skills + +| Skill | Path | What it covers | +| --- | --- | --- | +| Dev workflow | [skills/dev-workflow/SKILL.md](skills/dev-workflow/SKILL.md) | Branches, Maven lifecycle, CI expectations, PR flow. | +| Contentstack Java CMA SDK | [skills/contentstack-java-cma-sdk/SKILL.md](skills/contentstack-java-cma-sdk/SKILL.md) | Public API, `Contentstack` entry point, auth, versioning boundaries. | +| Java (repo conventions) | [skills/java/SKILL.md](skills/java/SKILL.md) | Package layout, language conventions, Lombok usage. | +| Testing | [skills/testing/SKILL.md](skills/testing/SKILL.md) | Test layout, naming, skipping policy, credentials. | +| Code review | [skills/code-review/SKILL.md](skills/code-review/SKILL.md) | PR checklist and severity guidance. | +| HTTP client stack | [skills/http-client-stack/SKILL.md](skills/http-client-stack/SKILL.md) | Retrofit, OkHttp, retries, logging—what this repo actually wires. | + +An index with “when to use” hints is in [skills/README.md](skills/README.md). + +## Using Cursor (optional) + +If you use *Cursor*, [.cursor/rules/README.md](.cursor/rules/README.md) only points to *AGENTS.md*—same docs as everyone else. diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 00000000..5d34656a --- /dev/null +++ b/skills/README.md @@ -0,0 +1,16 @@ +# Skills – Contentstack Management Java SDK + +Source of truth for detailed guidance. Read [AGENTS.md](../AGENTS.md) first, then open the skill that matches your task. + +## When to use which skill + +| Skill folder | Use when | +| --- | --- | +| [dev-workflow](dev-workflow/SKILL.md) | Branching against `master`/`staging`, running Maven/CI commands, release or publish touchpoints. | +| [contentstack-java-cma-sdk](contentstack-java-cma-sdk/SKILL.md) | Changing public API, `Contentstack` / `Stack` flows, auth tokens, or SDK surface exposed to integrators. | +| [java](java/SKILL.md) | Package structure under `com.contentstack.cms`, Java 8 compatibility, Lombok, imports, and code style in this repo. | +| [testing](testing/SKILL.md) | Adding or fixing tests, Surefire `skipTests` behavior, MockWebServer or live API tests, env/credentials. | +| [code-review](code-review/SKILL.md) | Preparing or reviewing a PR: scope, tests, API compatibility, and security around tokens. | +| [http-client-stack](http-client-stack/SKILL.md) | Retrofit services, OkHttp client/interceptors, retries, proxies, or HTTP logging. | + +Each folder contains `SKILL.md` with YAML frontmatter (`name`, `description`). diff --git a/skills/code-review/SKILL.md b/skills/code-review/SKILL.md new file mode 100644 index 00000000..906a50f3 --- /dev/null +++ b/skills/code-review/SKILL.md @@ -0,0 +1,42 @@ +--- +name: code-review +description: Use when reviewing or preparing a PR—API safety, tests, auth handling, and compatibility checklist. +--- + +# Code review – Contentstack Management Java SDK + +## When to use + +- You are reviewing a pull request or self-reviewing before request. +- You need a quick severity rubric (blocker / major / minor) aligned with this SDK. + +## Instructions + +### Blocker + +- Real credentials, tokens, or stack keys committed in source or fixtures. +- Breaking public API changes without version strategy or without team agreement. +- New network behavior that bypasses existing interceptors, retry policy, or auth without explicit design. + +### Major + +- Missing or insufficient tests for new CMA behavior or error paths. +- Behavior change for `Contentstack` / `Stack` builders or defaults without README or Javadoc updates where integrators would notice. +- Regressions for Java 8 compatibility or new compiler warnings left unaddressed. + +### Minor + +- Style inconsistencies fixable in follow-up, or internal refactors with no API impact. +- Javadoc typos or non-user-facing comment cleanup. + +### PR checklist (short) + +- [ ] `mvn clean test -DskipTests=false` passes locally. +- [ ] Public changes documented (Javadoc / README / changelog per team process). +- [ ] No secrets in repo; test config uses env or mocks. +- [ ] HTTP changes reviewed against [http-client-stack/SKILL.md](../http-client-stack/SKILL.md). + +## References + +- [dev-workflow/SKILL.md](../dev-workflow/SKILL.md) +- [contentstack-java-cma-sdk/SKILL.md](../contentstack-java-cma-sdk/SKILL.md) diff --git a/skills/contentstack-java-cma-sdk/SKILL.md b/skills/contentstack-java-cma-sdk/SKILL.md new file mode 100644 index 00000000..15501d47 --- /dev/null +++ b/skills/contentstack-java-cma-sdk/SKILL.md @@ -0,0 +1,35 @@ +--- +name: contentstack-java-cma-sdk +description: Use when changing the Management SDK surface—Contentstack entry, Stack, auth, or CMA integration boundaries. +--- + +# Contentstack Java CMA SDK – Contentstack Management Java SDK + +## When to use + +- You add or change public methods on `Contentstack`, `Stack`, organization, user, or other API-facing types under `com.contentstack.cms`. +- You need to document how integrators authenticate (authtoken, management token, OAuth, login). +- You are deciding what belongs in this SDK vs the Delivery API or other Contentstack clients. + +## Instructions + +### Purpose and boundaries + +- This library targets the [Content Management API](https://www.contentstack.com/docs/developers/apis/content-management-api/) (CMA). It is **not** the recommended way to deliver content to websites or apps at scale; the README directs readers to the [Content Delivery API](https://www.contentstack.com/docs/developers/apis/content-delivery-api/) for that. +- The main entry point is [`Contentstack`](../../src/main/java/com/contentstack/cms/Contentstack.java): builders set host, tokens, timeouts, proxy, OAuth, and retry behavior before obtaining `Stack`, `User`, `Organization`, etc. + +### Public API and compatibility + +- Treat additions to public classes as **semver-sensitive**: new optional builder methods are easier to ship than breaking signature changes. +- Errors and responses should stay consistent with existing patterns (`retrofit2.Response`, model types under `com.contentstack.cms.models`, etc.). +- Document behavior in Javadoc where integrators rely on contracts (auth order, token precedence, branch/early-access headers). + +### Versioning + +- Artifact coordinates: `com.contentstack.sdk:cms` — version in root [`pom.xml`](../../pom.xml). Follow org release process for bumping version and tagging. + +## References + +- [java/SKILL.md](../java/SKILL.md) +- [http-client-stack/SKILL.md](../http-client-stack/SKILL.md) +- [Content Management API – Authentication](https://www.contentstack.com/docs/developers/apis/content-management-api/#authentication) (official) diff --git a/skills/dev-workflow/SKILL.md b/skills/dev-workflow/SKILL.md new file mode 100644 index 00000000..3bfe3cc9 --- /dev/null +++ b/skills/dev-workflow/SKILL.md @@ -0,0 +1,41 @@ +--- +name: dev-workflow +description: Use when branching, building with Maven, CI, or release/publish workflows for this repo. +--- + +# Dev workflow – Contentstack Management Java SDK + +## When to use + +- You need the canonical build/test commands or CI expectations. +- You are opening a PR and need branch rules (`master` vs `staging`). +- You are changing `pom.xml`, plugins, or publishing configuration. + +## Instructions + +### Repository and branches + +- Default collaboration flow is documented in [.github/workflows/check-branch.yml](../../.github/workflows/check-branch.yml): PRs targeting `master` from branches other than `staging` may be blocked; prefer the documented Contentstack branching policy for your team. + +### Maven + +- **Compile:** `mvn clean compile` +- **Tests:** `mvn clean test -DskipTests=false` (Surefire is configured with `skipTests` true by default in [`pom.xml`](../../pom.xml); always pass `-DskipTests=false` for real test runs unless you intentionally skip.) +- **Package:** `mvn clean package` — note tests may be skipped unless you add `-DskipTests=false`. +- **Coverage report:** after tests with skip disabled, `mvn jacoco:report` and open `target/site/jacoco/index.html`. + +### CI + +- On push, [.github/workflows/coverage.yml](../../.github/workflows/coverage.yml) uses Java 11 on Ubuntu and runs Maven + JaCoCo. Align local verification with those steps when debugging CI-only failures. +- Publishing and security scans live under [.github/workflows/](../../.github/workflows/); read the relevant workflow before changing release or scan behavior. + +### PR expectations + +- Run tests locally with `-DskipTests=false` before pushing. +- Update [changelog.md](../../changelog.md) or version metadata when your team’s release process requires it. + +## References + +- [AGENTS.md](../../AGENTS.md) +- [testing/SKILL.md](../testing/SKILL.md) +- [code-review/SKILL.md](../code-review/SKILL.md) diff --git a/skills/http-client-stack/SKILL.md b/skills/http-client-stack/SKILL.md new file mode 100644 index 00000000..db8717ba --- /dev/null +++ b/skills/http-client-stack/SKILL.md @@ -0,0 +1,42 @@ +--- +name: http-client-stack +description: Use when changing Retrofit, OkHttp, interceptors, retries, proxies, or HTTP logging in this SDK. +--- + +# HTTP client stack – Contentstack Management Java SDK + +## When to use + +- You modify [`Contentstack`](../../src/main/java/com/contentstack/cms/Contentstack.java) or related code that builds `Retrofit`, `OkHttpClient`, or interceptors. +- You touch `AuthInterceptor`, OAuth interceptors, logging, connection pool, timeouts, or `RetryConfig` / retry utilities under `com.contentstack.cms.core`. + +## Instructions + +### Stack in this repo + +- **Retrofit 3** + **Gson** converter for API interfaces and JSON binding. +- **OkHttp 5** for transport; **logging-interceptor** for debug logging when enabled. +- **RxJava 3** is a dependency for async patterns where used; keep consistency with existing call patterns. + +### Auth and headers + +- `AuthInterceptor` and OAuth-related types attach tokens and headers expected by CMA. Changes here affect every call—coordinate with tests in `core/` (e.g. `AuthInterceptorTest`). + +### Retries and resilience + +- Retry behavior is configurable via `RetryConfig` and related utilities (`core/Retry*.java`). Preserve backward-compatible defaults when adjusting retry conditions or backoff. + +### Proxies and TLS + +- `Contentstack` supports `Proxy` configuration; verify behavior if you change client building or connection pool settings. + +### Testing + +- Prefer **MockWebServer** for deterministic HTTP tests; see existing stack and core tests for patterns. + +## References + +- [contentstack-java-cma-sdk/SKILL.md](../contentstack-java-cma-sdk/SKILL.md) +- [testing/SKILL.md](../testing/SKILL.md) +- [Retrofit](https://square.github.io/retrofit/) (official) +- [OkHttp](https://square.github.io/okhttp/) (official) diff --git a/skills/java/SKILL.md b/skills/java/SKILL.md new file mode 100644 index 00000000..59dfab06 --- /dev/null +++ b/skills/java/SKILL.md @@ -0,0 +1,37 @@ +--- +name: java +description: Use for Java package layout, Java 8 constraints, Lombok, and coding conventions in this repository. +--- + +# Java – Contentstack Management Java SDK + +## When to use + +- You add new types under `com.contentstack.cms` or reorganize packages. +- You need to stay compatible with Java 8 (`maven.compiler.source` / `target` = 1.8). +- You use or configure Lombok and need to match existing patterns. + +## Instructions + +### Layout + +- **Main code:** `src/main/java/com/contentstack/cms/` — core client in `Contentstack`, stack features under `stack/`, HTTP helpers in `core/`, OAuth under `oauth/`, models under `models/`. +- **Tests:** mirror packages under `src/test/java/com/contentstack/cms/`; Surefire includes `**/Test*.java`, `**/*Test.java`, `**/*Tests.java`, and related patterns per [`pom.xml`](../../pom.xml). + +### Language level + +- Do not use language or library features that require a JVM newer than 8 for **source** compatibility unless the project explicitly upgrades (check `maven-compiler-plugin` and `` in [`pom.xml`](../../pom.xml)). + +### Lombok and tooling + +- Lombok is on the annotation processor path; prefer the same style as neighboring classes (`@Getter`, `@Builder`, etc.) instead of mixing hand-written boilerplate inconsistently. +- JetBrains `@NotNull` / nullable annotations appear in places like [`Contentstack`](../../src/main/java/com/contentstack/cms/Contentstack.java); follow existing nullability hints for new APIs. + +### Compiler + +- The compiler enables broad `-Xlint` checks; fix new warnings rather than suppressing without cause. + +## References + +- [contentstack-java-cma-sdk/SKILL.md](../contentstack-java-cma-sdk/SKILL.md) +- [testing/SKILL.md](../testing/SKILL.md) diff --git a/skills/testing/SKILL.md b/skills/testing/SKILL.md new file mode 100644 index 00000000..d345a1f3 --- /dev/null +++ b/skills/testing/SKILL.md @@ -0,0 +1,37 @@ +--- +name: testing +description: Use when writing or running tests—Surefire skip flag, test naming, mocks, MockWebServer, and secrets policy. +--- + +# Testing – Contentstack Management Java SDK + +## When to use + +- You add or change unit or integration tests under `src/test/java`. +- Tests “do not run” locally or in CI — usually `skipTests`. +- You need to mock HTTP or use OkHttp `MockWebServer` like existing tests. + +## Instructions + +### Running tests + +- **Always** use `mvn test -DskipTests=false` (or `mvn clean test -DskipTests=false`) unless you are only compiling. The Surefire plugin in [`pom.xml`](../../pom.xml) sets `true` by default. + +### Layout and naming + +- Tests live under `src/test/java/com/contentstack/cms/` with class names matching Surefire includes (`*Test`, `*Tests`, `Test*`, suites like `UnitTestSuite`). +- Reuse helpers such as [`TestClient`](../../src/test/java/com/contentstack/cms/TestClient.java), [`TestUtils`](../../src/test/java/com/contentstack/cms/core/TestUtils.java), or patterns in existing `*APITest` classes when appropriate. + +### HTTP and API tests + +- **MockWebServer** is available for stubbing HTTP; align with tests in `core/` and `stack/` packages. +- Tests that hit real APIs may require environment variables or `.env` (see `java-dotenv` test dependency in [`pom.xml`](../../pom.xml)). **Never commit** real API keys, management tokens, or passwords; use CI secrets or local env only. + +### Coverage + +- JaCoCo attaches via `prepare-agent`; generate HTML with `mvn jacoco:report` after a test run with skips disabled. + +## References + +- [dev-workflow/SKILL.md](../dev-workflow/SKILL.md) +- [http-client-stack/SKILL.md](../http-client-stack/SKILL.md) From 61c9800eccf1fc6adda15dcc4d9d19dbca64b035 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 7 Apr 2026 12:52:55 +0530 Subject: [PATCH 183/197] snyk fixes --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 2feb459a..c227e9ba 100644 --- a/pom.xml +++ b/pom.xml @@ -93,7 +93,7 @@ 3.0.0 5.3.2 0.8.13 - 1.18.42 + 1.18.44 5.11.4 5.10.1 2.13.2 @@ -148,7 +148,7 @@ org.jetbrains annotations - 26.0.2 + 26.1.0 provided @@ -214,12 +214,12 @@ org.jsoup jsoup - 1.21.2 + 1.22.1 org.jetbrains.kotlin kotlin-stdlib - 2.2.21 + 2.3.20 com.warrenstrange @@ -254,7 +254,7 @@ ${project.build.directory}/surefire-reports - true + true From 77c4118bb8a40afeeda584d1930114b9e03f826e Mon Sep 17 00:00:00 2001 From: harshitha-cstk Date: Thu, 23 Apr 2026 17:12:04 +0530 Subject: [PATCH 184/197] =?UTF-8?q?enh:=20adopt=20development=E2=86=92main?= =?UTF-8?q?=20release=20flow,=20back-merge,=20and=20version=20checks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/back-merge-pr.yml | 59 ++++++++++++ .github/workflows/check-branch.yml | 20 ---- .github/workflows/check-version-bump.yml | 111 +++++++++++++++++++++++ .github/workflows/maven-publish.yml | 2 + skills/README.md | 2 +- skills/dev-workflow/SKILL.md | 11 +-- 6 files changed, 176 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/back-merge-pr.yml delete mode 100644 .github/workflows/check-branch.yml create mode 100644 .github/workflows/check-version-bump.yml diff --git a/.github/workflows/back-merge-pr.yml b/.github/workflows/back-merge-pr.yml new file mode 100644 index 00000000..cec0f269 --- /dev/null +++ b/.github/workflows/back-merge-pr.yml @@ -0,0 +1,59 @@ +# Opens a PR from master → development after changes land on master (back-merge). +# +# Org/repo Settings → Actions → General → Workflow permissions: read and write +# (so GITHUB_TOKEN can create pull requests). Or use a PAT in secret GH_TOKEN. + +name: Back-merge master to development + +on: + push: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + +jobs: + open-back-merge-pr: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Open back-merge PR if needed + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + git fetch origin development master + + MASTER_SHA=$(git rev-parse origin/master) + DEV_SHA=$(git rev-parse origin/development) + + if [ "$MASTER_SHA" = "$DEV_SHA" ]; then + echo "master and development are at the same commit; nothing to back-merge." + exit 0 + fi + + EXISTING=$(gh pr list --repo "${{ github.repository }}" \ + --base development \ + --head master \ + --state open \ + --json number \ + --jq 'length') + + if [ "$EXISTING" -gt 0 ]; then + echo "An open PR from master to development already exists; skipping." + exit 0 + fi + + gh pr create --repo "${{ github.repository }}" \ + --base development \ + --head master \ + --title "chore: back-merge master into development" \ + --body "Automated back-merge after changes landed on \`master\`. Review and merge to keep \`development\` in sync." + + echo "Created back-merge PR master → development." diff --git a/.github/workflows/check-branch.yml b/.github/workflows/check-branch.yml deleted file mode 100644 index 8a3a32ab..00000000 --- a/.github/workflows/check-branch.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: 'Check Branch' - -on: - pull_request: - -jobs: - check_branch: - runs-on: ubuntu-latest - steps: - - name: Comment PR - if: github.base_ref == 'master' && github.head_ref != 'staging' - uses: thollander/actions-comment-pull-request@v2 - with: - message: | - We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the staging branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch. - - name: Check branch - if: github.base_ref == 'master' && github.head_ref != 'staging' - run: | - echo "ERROR: We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the staging branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch." - exit 1 \ No newline at end of file diff --git a/.github/workflows/check-version-bump.yml b/.github/workflows/check-version-bump.yml new file mode 100644 index 00000000..9c7d3136 --- /dev/null +++ b/.github/workflows/check-version-bump.yml @@ -0,0 +1,111 @@ +# Release-affecting changes under src/main/ or pom.xml require pom.xml + changelog.md bumps +# aligned with the latest tag. Skips when only tests, .github, skills, or docs change. + +name: Check Version Bump + +on: + pull_request: + +jobs: + version-bump: + name: Version & changelog bump + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect changed files + id: detect + run: | + FILES=$(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}") + echo "Changed files:" + echo "$FILES" + + CODE_CHANGED=false + while IFS= read -r f; do + [ -z "$f" ] && continue + if [[ "$f" == src/main/* ]] || [[ "$f" == "pom.xml" ]]; then + CODE_CHANGED=true + break + fi + done <<< "$FILES" + + POM_CHANGED=false + CHANGELOG_CHANGED=false + echo "$FILES" | grep -qx 'pom.xml' && POM_CHANGED=true + echo "$FILES" | grep -qx 'changelog.md' && CHANGELOG_CHANGED=true + + VERSION_FILES_OK=false + if [ "$POM_CHANGED" = true ] && [ "$CHANGELOG_CHANGED" = true ]; then + VERSION_FILES_OK=true + fi + + echo "code_changed=$CODE_CHANGED" >> "$GITHUB_OUTPUT" + echo "version_files_ok=$VERSION_FILES_OK" >> "$GITHUB_OUTPUT" + + - name: Skip when no release-affecting code changed + if: steps.detect.outputs.code_changed != 'true' + run: | + echo "No src/main or pom-only release path triggered (e.g. tests/docs/.github only). Skipping version-bump check." + exit 0 + + - name: Fail when version bump files were not both updated + if: steps.detect.outputs.code_changed == 'true' && steps.detect.outputs.version_files_ok != 'true' + run: | + echo "::error::This PR changes release-affecting code but pom.xml and/or changelog.md were not both updated. Bump in pom.xml and add a ## vX.Y.Z section in changelog.md." + exit 1 + + - name: Validate version vs latest tag and changelog header + if: steps.detect.outputs.code_changed == 'true' && steps.detect.outputs.version_files_ok == 'true' + run: | + set -euo pipefail + POM_VERSION=$(python3 <<'PY' + import xml.etree.ElementTree as ET + root = ET.parse("pom.xml").getroot() + ns = {"m": "http://maven.apache.org/POM/4.0.0"} + el = root.find("m:version", ns) + if el is None or not (el.text or "").strip(): + raise SystemExit("Could not read project version from pom.xml") + print(el.text.strip()) + PY + ) + + git fetch --tags --force 2>/dev/null || true + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || true) + if [ -z "$LATEST_TAG" ]; then + echo "No existing tags found. Skipping semver vs tag check (first release)." + CHANGELOG_HEAD=$(sed -nE 's/^## v?([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' changelog.md | head -1) + if [ -z "$CHANGELOG_HEAD" ]; then + echo "::error::Could not find a ## vX.Y.Z entry at the top of changelog.md." + exit 1 + fi + if [ "$CHANGELOG_HEAD" != "$POM_VERSION" ]; then + echo "::error::changelog.md top version ($CHANGELOG_HEAD) does not match pom.xml version ($POM_VERSION)." + exit 1 + fi + exit 0 + fi + + LATEST_VERSION="${LATEST_TAG#v}" + LATEST_VERSION="${LATEST_VERSION%%-*}" + if [ "$(printf '%s\n' "$LATEST_VERSION" "$POM_VERSION" | sort -V | tail -1)" != "$POM_VERSION" ]; then + echo "::error::pom.xml version ($POM_VERSION) must be greater than latest tag ($LATEST_TAG)." + exit 1 + fi + if [ "$POM_VERSION" = "$LATEST_VERSION" ]; then + echo "::error::pom.xml version ($POM_VERSION) must be strictly greater than latest tag version ($LATEST_VERSION)." + exit 1 + fi + + CHANGELOG_HEAD=$(sed -nE 's/^## v?([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' changelog.md | head -1) + if [ -z "$CHANGELOG_HEAD" ]; then + echo "::error::Could not find a ## vX.Y.Z entry at the top of changelog.md." + exit 1 + fi + if [ "$CHANGELOG_HEAD" != "$POM_VERSION" ]; then + echo "::error::changelog.md top version ($CHANGELOG_HEAD) does not match pom.xml version ($POM_VERSION)." + exit 1 + fi + echo "Version bump check passed: pom.xml and changelog.md at $POM_VERSION (latest tag: $LATEST_TAG)." diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 4c5577e1..e0bc9053 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -1,9 +1,11 @@ name: Publish package to the Maven Central Repository +# Publishes when a GitHub Release is created (same pattern as before tag-based experiments). on: release: types: - created + jobs: publish-maven: runs-on: ubuntu-latest diff --git a/skills/README.md b/skills/README.md index 5d34656a..0a5b2cbc 100644 --- a/skills/README.md +++ b/skills/README.md @@ -6,7 +6,7 @@ Source of truth for detailed guidance. Read [AGENTS.md](../AGENTS.md) first, the | Skill folder | Use when | | --- | --- | -| [dev-workflow](dev-workflow/SKILL.md) | Branching against `master`/`staging`, running Maven/CI commands, release or publish touchpoints. | +| [dev-workflow](dev-workflow/SKILL.md) | Branching (`development` → `master`, back-merge, GitHub Release publish), Maven/CI, publish touchpoints. | | [contentstack-java-cma-sdk](contentstack-java-cma-sdk/SKILL.md) | Changing public API, `Contentstack` / `Stack` flows, auth tokens, or SDK surface exposed to integrators. | | [java](java/SKILL.md) | Package structure under `com.contentstack.cms`, Java 8 compatibility, Lombok, imports, and code style in this repo. | | [testing](testing/SKILL.md) | Adding or fixing tests, Surefire `skipTests` behavior, MockWebServer or live API tests, env/credentials. | diff --git a/skills/dev-workflow/SKILL.md b/skills/dev-workflow/SKILL.md index 3bfe3cc9..66a5194c 100644 --- a/skills/dev-workflow/SKILL.md +++ b/skills/dev-workflow/SKILL.md @@ -8,14 +8,15 @@ description: Use when branching, building with Maven, CI, or release/publish wor ## When to use - You need the canonical build/test commands or CI expectations. -- You are opening a PR and need branch rules (`master` vs `staging`). +- You are opening a PR and need branch rules (`development` → `master`, GitHub Release publishing). - You are changing `pom.xml`, plugins, or publishing configuration. ## Instructions ### Repository and branches -- Default collaboration flow is documented in [.github/workflows/check-branch.yml](../../.github/workflows/check-branch.yml): PRs targeting `master` from branches other than `staging` may be blocked; prefer the documented Contentstack branching policy for your team. +- **Flow:** work merges to **`development`**; **release PRs** go **`development` → `master`** (no `staging`). After `master` moves, [.github/workflows/back-merge-pr.yml](../../.github/workflows/back-merge-pr.yml) can open a PR **`master` → `development`** to stay aligned. +- **Releases:** create a **GitHub Release** (triggers [.github/workflows/maven-publish.yml](../../.github/workflows/maven-publish.yml) on **`release: created`**). PRs that change `src/main` or `pom.xml` are checked by [.github/workflows/check-version-bump.yml](../../.github/workflows/check-version-bump.yml) (version + `changelog.md`). ### Maven @@ -33,9 +34,3 @@ description: Use when branching, building with Maven, CI, or release/publish wor - Run tests locally with `-DskipTests=false` before pushing. - Update [changelog.md](../../changelog.md) or version metadata when your team’s release process requires it. - -## References - -- [AGENTS.md](../../AGENTS.md) -- [testing/SKILL.md](../testing/SKILL.md) -- [code-review/SKILL.md](../code-review/SKILL.md) From 1248c9442c98a76aa6732383c9f29e13222aa0eb Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 26 May 2026 17:55:33 +0530 Subject: [PATCH 185/197] fix: enhance retry mechanism for network-level timeouts in AuthInterceptor and OAuthInterceptor; update version to 1.11.2 --- changelog.md | 6 + pom.xml | 2 +- .../cms/core/AuthInterceptor.java | 41 ++++-- .../cms/oauth/OAuthInterceptor.java | 18 ++- .../cms/core/AuthInterceptorTest.java | 84 +++++++++++++ .../cms/oauth/OAuthInterceptorTest.java | 118 ++++++++++++++++++ 6 files changed, 254 insertions(+), 15 deletions(-) create mode 100644 src/test/java/com/contentstack/cms/oauth/OAuthInterceptorTest.java diff --git a/changelog.md b/changelog.md index 80f1373b..b69d012c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## v1.11.2 + +### Jun 01, 2026 + +- Fix: `SocketTimeoutException` now correctly triggers the retry mechanism in `AuthInterceptor` and `OAuthInterceptor`. Previously, network-level timeouts bypassed retry logic entirely, causing `.setRetry(true)` to have no effect on timeout errors. + ## v1.11.1 ### Apr 06, 2026 diff --git a/pom.xml b/pom.xml index c227e9ba..1ff53c24 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.11.1 + 1.11.2 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach diff --git a/src/main/java/com/contentstack/cms/core/AuthInterceptor.java b/src/main/java/com/contentstack/cms/core/AuthInterceptor.java index 80ee0f97..873f5912 100644 --- a/src/main/java/com/contentstack/cms/core/AuthInterceptor.java +++ b/src/main/java/com/contentstack/cms/core/AuthInterceptor.java @@ -1,6 +1,7 @@ package com.contentstack.cms.core; import java.io.IOException; +import java.net.SocketTimeoutException; import org.jetbrains.annotations.NotNull; @@ -116,21 +117,35 @@ public void setRetryConfig(RetryConfig retryConfig) { this.retryConfig = retryConfig != null ? retryConfig : RetryConfig.defaultConfig(); } - private Response executeRequest(Chain chain, Request request, int retryCount) throws IOException{ - Response response = chain.proceed(request); - int code = response.code(); - if(retryCount < retryConfig.getRetryLimit() && retryConfig.getRetryCondition().shouldRetry(code, null)){ - response.close(); - long delay = RetryUtil.calculateDelay(retryConfig, retryCount+1, code); - try { - Thread.sleep(delay); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - throw new IOException("Retry interrupted", ex); + private Response executeRequest(Chain chain, Request request, int retryCount) throws IOException { + try { + Response response = chain.proceed(request); + int code = response.code(); + if (retryCount < retryConfig.getRetryLimit() && retryConfig.getRetryCondition().shouldRetry(code, null)) { + response.close(); + long delay = RetryUtil.calculateDelay(retryConfig, retryCount + 1, code); + try { + Thread.sleep(delay); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IOException("Retry interrupted", ex); + } + return executeRequest(chain, request, retryCount + 1); } - return executeRequest(chain, request, retryCount + 1); + return response; + } catch (SocketTimeoutException e) { + if (retryCount < retryConfig.getRetryLimit() && retryConfig.getRetryCondition().shouldRetry(0, e)) { + long delay = RetryUtil.calculateDelay(retryConfig, retryCount + 1, 0); + try { + Thread.sleep(delay); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IOException("Retry interrupted", ex); + } + return executeRequest(chain, request, retryCount + 1); + } + throw e; } - return response; } } diff --git a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java index 56f110a9..8feb5c63 100644 --- a/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java +++ b/src/main/java/com/contentstack/cms/oauth/OAuthInterceptor.java @@ -1,6 +1,7 @@ package com.contentstack.cms.oauth; import java.io.IOException; +import java.net.SocketTimeoutException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -112,7 +113,22 @@ private Response executeRequest(Chain chain, Request request, int retryCount) th } // Execute request - Response response = chain.proceed(request); + Response response; + try { + response = chain.proceed(request); + } catch (SocketTimeoutException e) { + if (retryCount < retryConfig.getRetryLimit() && retryConfig.getRetryCondition().shouldRetry(0, e)) { + long delay = RetryUtil.calculateDelay(retryConfig, retryCount + 1, 0); + try { + Thread.sleep(delay); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IOException("Retry interrupted", ex); + } + return executeRequest(chain, request, retryCount + 1); + } + throw e; + } // Handle error responses if (!response.isSuccessful() && retryCount < retryConfig.getRetryLimit()) { diff --git a/src/test/java/com/contentstack/cms/core/AuthInterceptorTest.java b/src/test/java/com/contentstack/cms/core/AuthInterceptorTest.java index 310f964d..efe1b867 100644 --- a/src/test/java/com/contentstack/cms/core/AuthInterceptorTest.java +++ b/src/test/java/com/contentstack/cms/core/AuthInterceptorTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; import java.io.IOException; +import java.net.SocketTimeoutException; public class AuthInterceptorTest { @@ -166,6 +167,89 @@ public Call call() { } } + @Test + @Tag("unit") + public void testRetry_onSocketTimeout_thenSuccess_retriesAndReturnsSuccess() throws IOException { + authInterceptor.setRetryConfig(RetryConfig.builder().retryLimit(3).retryDelay(10).build()); + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/user") + .get() + .build(); + TimeoutTestChain chain = new TimeoutTestChain(request, 1, 200); + try (Response response = authInterceptor.intercept(chain)) { + Assertions.assertEquals(200, response.code()); + Assertions.assertEquals(2, chain.getProceedCount()); + } + } + + @Test + @Tag("unit") + public void testRetry_onSocketTimeout_exhaustsRetries_throws() { + authInterceptor.setRetryConfig(RetryConfig.builder().retryLimit(2).retryDelay(10).build()); + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/user") + .get() + .build(); + TimeoutTestChain chain = new TimeoutTestChain(request, 5, 200); + Assertions.assertThrows(SocketTimeoutException.class, () -> authInterceptor.intercept(chain)); + Assertions.assertEquals(3, chain.getProceedCount()); + } + + @Test + @Tag("unit") + public void testRetry_onSocketTimeout_zeroRetryLimit_throwsImmediately() { + authInterceptor.setRetryConfig(RetryConfig.builder().retryLimit(0).retryDelay(10).build()); + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/user") + .get() + .build(); + TimeoutTestChain chain = new TimeoutTestChain(request, 5, 200); + Assertions.assertThrows(SocketTimeoutException.class, () -> authInterceptor.intercept(chain)); + Assertions.assertEquals(1, chain.getProceedCount()); + } + + private static class TimeoutTestChain implements Interceptor.Chain { + private final Request originalRequest; + private final int timeoutCount; + private final int successCode; + private int proceedCount = 0; + + TimeoutTestChain(Request request, int timeoutCount, int successCode) { + this.originalRequest = request; + this.timeoutCount = timeoutCount; + this.successCode = successCode; + } + + int getProceedCount() { return proceedCount; } + + @Override + public Request request() { return originalRequest; } + + @Override + public Response proceed(Request request) throws IOException { + proceedCount++; + if (proceedCount <= timeoutCount) { + throw new SocketTimeoutException("timeout"); + } + return new Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(successCode) + .message("OK") + .body(ResponseBody.create("{}", MediaType.parse("application/json"))) + .build(); + } + + @Override public Connection connection() { return null; } + @Override public int connectTimeoutMillis() { return 0; } + @Override public Interceptor.Chain withConnectTimeout(int timeout, java.util.concurrent.TimeUnit unit) { return this; } + @Override public int readTimeoutMillis() { return 0; } + @Override public Interceptor.Chain withReadTimeout(int timeout, java.util.concurrent.TimeUnit unit) { return this; } + @Override public int writeTimeoutMillis() { return 0; } + @Override public Interceptor.Chain withWriteTimeout(int timeout, java.util.concurrent.TimeUnit unit) { return this; } + @Override public Call call() { return null; } + } + @Test public void AuthInterceptor() { AuthInterceptor expected = new AuthInterceptor("abc"); diff --git a/src/test/java/com/contentstack/cms/oauth/OAuthInterceptorTest.java b/src/test/java/com/contentstack/cms/oauth/OAuthInterceptorTest.java new file mode 100644 index 00000000..580f9268 --- /dev/null +++ b/src/test/java/com/contentstack/cms/oauth/OAuthInterceptorTest.java @@ -0,0 +1,118 @@ +package com.contentstack.cms.oauth; + +import com.contentstack.cms.core.RetryConfig; +import com.contentstack.cms.models.OAuthTokens; +import okhttp3.*; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import java.io.IOException; +import java.net.SocketTimeoutException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +@RunWith(MockitoJUnitRunner.class) +public class OAuthInterceptorTest { + + private OAuthInterceptor interceptor; + + @Mock + private OAuthHandler mockHandler; + + @Mock + private OAuthTokens mockTokens; + + @Before + public void setup() { + Mockito.lenient().when(mockTokens.isExpired()).thenReturn(false); + Mockito.lenient().when(mockTokens.hasAccessToken()).thenReturn(true); + Mockito.lenient().when(mockHandler.getTokens()).thenReturn(mockTokens); + Mockito.lenient().when(mockHandler.getAccessToken()).thenReturn("test-access-token"); + + interceptor = new OAuthInterceptor(mockHandler); + interceptor.setRetryConfig(RetryConfig.builder().retryLimit(3).retryDelay(10).build()); + } + + @Test + public void testRetry_onSocketTimeout_thenSuccess_retriesAndReturnsSuccess() throws IOException { + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/content_types") + .get() + .build(); + TimeoutTestChain chain = new TimeoutTestChain(request, 1, 200); + try (Response response = interceptor.intercept(chain)) { + assertEquals(200, response.code()); + assertEquals(2, chain.getProceedCount()); + } + } + + @Test + public void testRetry_onSocketTimeout_exhaustsRetries_throws() { + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/content_types") + .get() + .build(); + TimeoutTestChain chain = new TimeoutTestChain(request, 5, 200); + assertThrows(SocketTimeoutException.class, () -> interceptor.intercept(chain)); + assertEquals(4, chain.getProceedCount()); // 1 initial + 3 retries + } + + @Test + public void testRetry_onSocketTimeout_zeroRetryLimit_throwsImmediately() { + interceptor.setRetryConfig(RetryConfig.builder().retryLimit(0).retryDelay(10).build()); + Request request = new Request.Builder() + .url("https://api.contentstack.io/v3/content_types") + .get() + .build(); + TimeoutTestChain chain = new TimeoutTestChain(request, 5, 200); + assertThrows(SocketTimeoutException.class, () -> interceptor.intercept(chain)); + assertEquals(1, chain.getProceedCount()); + } + + private static class TimeoutTestChain implements Interceptor.Chain { + private final Request originalRequest; + private final int timeoutCount; + private final int successCode; + private int proceedCount = 0; + + TimeoutTestChain(Request request, int timeoutCount, int successCode) { + this.originalRequest = request; + this.timeoutCount = timeoutCount; + this.successCode = successCode; + } + + int getProceedCount() { return proceedCount; } + + @Override + public Request request() { return originalRequest; } + + @Override + public Response proceed(Request request) throws IOException { + proceedCount++; + if (proceedCount <= timeoutCount) { + throw new SocketTimeoutException("timeout"); + } + return new Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(successCode) + .message("OK") + .body(ResponseBody.create("{}", MediaType.parse("application/json"))) + .build(); + } + + @Override public Connection connection() { return null; } + @Override public int connectTimeoutMillis() { return 0; } + @Override public Interceptor.Chain withConnectTimeout(int timeout, java.util.concurrent.TimeUnit unit) { return this; } + @Override public int readTimeoutMillis() { return 0; } + @Override public Interceptor.Chain withReadTimeout(int timeout, java.util.concurrent.TimeUnit unit) { return this; } + @Override public int writeTimeoutMillis() { return 0; } + @Override public Interceptor.Chain withWriteTimeout(int timeout, java.util.concurrent.TimeUnit unit) { return this; } + @Override public Call call() { return null; } + } +} From e95ee1eac472012bccf1531adfac41f71188e49c Mon Sep 17 00:00:00 2001 From: Aravind Kumar Date: Tue, 26 May 2026 18:25:07 +0530 Subject: [PATCH 186/197] Update README.md From 0c9681e5c45c0596832332ff6c7e929cc9de2209 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Thu, 28 May 2026 12:51:42 +0530 Subject: [PATCH 187/197] fix: enhance retry mechanism for network-level timeouts in AuthInterceptor and OAuthInterceptor; update version to 1.11.2 --- changelog.md | 1 + .../com/contentstack/cms/Contentstack.java | 12 +++++++++++ .../cms/ContentstackUnitTest.java | 21 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/changelog.md b/changelog.md index b69d012c..74e2cc78 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ ### Jun 01, 2026 - Fix: `SocketTimeoutException` now correctly triggers the retry mechanism in `AuthInterceptor` and `OAuthInterceptor`. Previously, network-level timeouts bypassed retry logic entirely, causing `.setRetry(true)` to have no effect on timeout errors. +- Enhancement: Added `setProtocols(List)` to the Builder, allowing callers to restrict the HTTP protocol (e.g. force HTTP/1.1 via `Collections.singletonList(Protocol.HTTP_1_1)`) for environments where proxies or intermediaries have issues with HTTP/2. ## v1.11.1 diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index ebe847b3..9c497a9e 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -3,7 +3,9 @@ import java.io.IOException; import java.net.Proxy; import java.time.Duration; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -30,6 +32,7 @@ import com.contentstack.cms.core.RetryConfig; import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; +import okhttp3.Protocol; import okhttp3.ResponseBody; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Response; @@ -609,6 +612,7 @@ public static class Builder { * evicted after 5 minutes of inactivity. */ private ConnectionPool connectionPool = new ConnectionPool(); // Connection + private List protocols = null; /** * Instantiates a new Builder. @@ -713,6 +717,11 @@ public Builder setConnectTimeout(int connectTimeoutSeconds) { return this; } + public Builder setProtocols(@NotNull List protocols) { + this.protocols = protocols; + return this; + } + private static void validateTimeoutSeconds(int seconds, String name) { if (seconds <= 0) { throw new IllegalArgumentException(name + " must be positive."); @@ -881,6 +890,9 @@ private OkHttpClient httpClient(Contentstack contentstack, Boolean retryOnFailur .readTimeout(Duration.ofSeconds(readSec)) .writeTimeout(Duration.ofSeconds(writeSec)) .retryOnConnectionFailure(retryOnFailure); + if (this.protocols != null && !this.protocols.isEmpty()) { + builder.protocols(this.protocols); + } // Add either OAuth or traditional auth interceptor if (this.oauthConfig != null) { diff --git a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java index 9e84c9b8..1da49474 100644 --- a/src/test/java/com/contentstack/cms/ContentstackUnitTest.java +++ b/src/test/java/com/contentstack/cms/ContentstackUnitTest.java @@ -5,6 +5,7 @@ import com.contentstack.cms.stack.Stack; import okhttp3.Headers; import okhttp3.OkHttpClient; +import okhttp3.Protocol; import okhttp3.Request; import okhttp3.ResponseBody; import okhttp3.mockwebserver.MockResponse; @@ -17,7 +18,9 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -160,6 +163,24 @@ void setVersion() { Assertions.assertEquals("v8", contentstack.version); } + @Test + void setProtocols_http1Only_buildsSuccessfully() { + List protocols = Collections.singletonList(Protocol.HTTP_1_1); + Contentstack contentstack = new Contentstack.Builder() + .setProtocols(protocols) + .build(); + Assertions.assertNotNull(contentstack); + } + + @Test + void setProtocols_http2AndHttp1_buildsSuccessfully() { + List protocols = List.of(Protocol.HTTP_2, Protocol.HTTP_1_1); + Contentstack contentstack = new Contentstack.Builder() + .setProtocols(protocols) + .build(); + Assertions.assertNotNull(contentstack); + } + @Test void setTimeout() { Contentstack contentstack = new Contentstack.Builder() From 4a0bf2d83d4d5ded721567fb1d6a70a8b4700ce4 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Fri, 5 Jun 2026 13:54:08 +0530 Subject: [PATCH 188/197] feat: Add dynamic endpoint resolution and region management - Introduced dynamic endpoint resolution via `Endpoint.getContentstackEndpoint()` - Added convenience method `Contentstack.Builder.setRegion(String)` for region targeting - Implemented static proxy methods for ad-hoc URL resolution in `Contentstack` - Added `Contentstack.refreshRegions()` to refresh regions registry from live source - Bundled `regions.json` in JAR and auto-refreshed during build - Added public accessors `Contentstack.getHost()` and `Contentstack.getBaseUrl()` - Created `Endpoint` class to manage endpoint resolution and caching - Implemented tests for endpoint resolution and region management --- changelog.md | 11 + pom.xml | 22 +- scripts/download-regions.sh | 48 ++ .../com/contentstack/cms/Contentstack.java | 110 +++++ .../com/contentstack/cms/core/Endpoint.java | 238 ++++++++++ .../java/com/contentstack/cms/TestClient.java | 1 + .../com/contentstack/cms/UnitTestSuite.java | 2 + .../contentstack/cms/core/EndpointTest.java | 411 ++++++++++++++++++ .../cms/stack/APISanityTestSuite.java | 3 +- .../cms/stack/EndpointAPITest.java | 212 +++++++++ 10 files changed, 1056 insertions(+), 2 deletions(-) create mode 100755 scripts/download-regions.sh create mode 100644 src/main/java/com/contentstack/cms/core/Endpoint.java create mode 100644 src/test/java/com/contentstack/cms/core/EndpointTest.java create mode 100644 src/test/java/com/contentstack/cms/stack/EndpointAPITest.java diff --git a/changelog.md b/changelog.md index 74e2cc78..d6e917fd 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,16 @@ # Changelog +## v1.12.0 + +### Jun 05, 2026 + +- Feature: Dynamic endpoint resolution via `Endpoint.getContentstackEndpoint()` backed by the Contentstack Regions Registry (`https://artifacts.contentstack.com/regions.json`). Resolves the correct API URL for any of the 7 supported regions (NA, EU, AU, Azure NA, Azure EU, GCP NA, GCP EU) and 18 service keys without hardcoding host strings. +- Feature: `Contentstack.Builder.setRegion(String)` — convenience method to target a region directly (e.g. `.setRegion("eu")`), automatically resolving the correct Content Management API host. +- Feature: `Contentstack.getContentstackEndpoint(region, service)` and `Contentstack.getContentstackEndpoints(region)` static proxy methods for ad-hoc URL resolution. +- Feature: `Contentstack.refreshRegions()` — forces a live download of the regions registry and refreshes the in-memory cache, so newly published regions or service URLs are available without upgrading the SDK. +- Feature: `regions.json` bundled in the JAR and auto-refreshed at build time via `scripts/download-regions.sh` (invoked on the Maven `generate-resources` phase). +- Feature: `Contentstack.getHost()` and `Contentstack.getBaseUrl()` public accessors on the client instance. + ## v1.11.2 ### Jun 01, 2026 diff --git a/pom.xml b/pom.xml index 1ff53c24..1f203bed 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.11.2 + 1.12.0 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach @@ -278,6 +278,26 @@ + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + download-regions + generate-resources + + exec + + + bash + + ${project.basedir}/scripts/download-regions.sh + + + + + org.apache.maven.plugins maven-source-plugin diff --git a/scripts/download-regions.sh b/scripts/download-regions.sh new file mode 100755 index 00000000..60a76418 --- /dev/null +++ b/scripts/download-regions.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# +# Downloads the Contentstack regions registry from the official source and +# saves it to src/main/resources/regions.json. +# +# Invoked automatically by Maven on the generate-resources phase, and +# manually via: bash scripts/download-regions.sh +# +# Requires: curl (preferred) or wget as fallback + +set -euo pipefail + +URL="https://artifacts.contentstack.com/regions.json" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEST="${SCRIPT_DIR}/../src/main/resources/regions.json" +DIR="$(dirname "$DEST")" + +mkdir -p "$DIR" + +data="" + +# --- Attempt 1: curl (preferred) -------------------------------------------- +if command -v curl &>/dev/null; then + data=$(curl --silent --fail --location --max-time 30 "$URL") || data="" +fi + +# --- Attempt 2: wget fallback ----------------------------------------------- +if [[ -z "$data" ]] && command -v wget &>/dev/null; then + data=$(wget --quiet --timeout=30 -O - "$URL") || data="" +fi + +# --- Validate and write ------------------------------------------------------ +if [[ -z "$data" ]]; then + echo "contentstack/cms: Warning — could not download regions.json." >&2 + echo " The SDK will attempt to download it at runtime on first use." >&2 + exit 0 # non-fatal: runtime fallback in Endpoint.java handles it +fi + +# Basic validation: must contain a "regions" key +if ! echo "$data" | grep -q '"regions"'; then + echo "contentstack/cms: Warning — downloaded data is not valid regions.json." >&2 + exit 0 +fi + +echo "$data" > "$DEST" + +region_count=$(echo "$data" | grep -o '"id"' | wc -l | tr -d ' ') +echo "contentstack/cms: regions.json downloaded (${region_count} regions)." diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 9c497a9e..2603b760 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull; import com.contentstack.cms.core.AuthInterceptor; +import com.contentstack.cms.core.Endpoint; import com.contentstack.cms.core.Util; import static com.contentstack.cms.core.Util.API_KEY; import static com.contentstack.cms.core.Util.AUTHORIZATION; @@ -561,6 +562,81 @@ public CompletableFuture oauthLogout() { return oauthLogout(false); } + /** + * Forces a live download of the Contentstack regions registry and replaces + * the in-memory cache. Useful when new regions or service URLs have been + * published since the SDK JAR was built. + * + *
    {@code
    +     * int count = Contentstack.refreshRegions();
    +     * // now getContentstackEndpoint() resolves from the freshly downloaded registry
    +     * }
    + * + * @return the number of regions loaded from the live registry + * @throws RuntimeException if the download fails + */ + public static int refreshRegions() { + return Endpoint.refresh(); + } + + /** + * Resolves a Contentstack service endpoint URL for the given region. + * + *
    {@code
    +     * // Full URL
    +     * String url = Contentstack.getContentstackEndpoint("eu", "contentManagement");
    +     * // → "https://eu-api.contentstack.com"
    +     *
    +     * // Host only (for Builder.setHost)
    +     * String host = Contentstack.getContentstackEndpoint("eu", "contentManagement", true);
    +     * // → "eu-api.contentstack.com"
    +     * }
    + * + * @param region region ID or alias (e.g. {@code na}, {@code eu}, {@code azure-na}) + * @param service service key (e.g. {@code contentManagement}, {@code contentDelivery}) + * @param omitHttps when {@code true}, strips {@code https://} from the returned URL + * @return the resolved URL string + * @throws IllegalArgumentException if region or service is unknown + */ + public static String getContentstackEndpoint(String region, String service, boolean omitHttps) { + return Endpoint.getContentstackEndpoint(region, service, omitHttps); + } + + /** + * Resolves a Contentstack service endpoint URL for the given region (with scheme). + * + * @param region region ID or alias + * @param service service key + * @return the resolved URL including {@code https://} + * @throws IllegalArgumentException if region or service is unknown + */ + public static String getContentstackEndpoint(String region, String service) { + return Endpoint.getContentstackEndpoint(region, service); + } + + /** + * Returns all endpoint URLs for the given region as an ordered map. + * + * @param region region ID or alias + * @return map of service name → URL (includes {@code https://}) + * @throws IllegalArgumentException if region is unknown or empty + */ + public static java.util.Map getContentstackEndpoints(String region) { + return Endpoint.getContentstackEndpoints(region); + } + + /** + * Returns all endpoint URLs for the given region as an ordered map. + * + * @param region region ID or alias + * @param omitHttps when {@code true}, strips {@code https://} from every URL + * @return map of service name → URL + * @throws IllegalArgumentException if region is unknown or empty + */ + public static java.util.Map getContentstackEndpoints(String region, boolean omitHttps) { + return Endpoint.getContentstackEndpoints(region, omitHttps); + } + public Contentstack(Builder builder) { this.host = builder.hostname; this.port = builder.port; @@ -577,6 +653,16 @@ public Contentstack(Builder builder) { this.retryConfig = builder.retryConfig; } + /** Returns the API hostname this client is configured to target. */ + public String getHost() { + return host; + } + + /** Returns the full Retrofit base URL (e.g. {@code https://eu-api.contentstack.com/v3/}). */ + public String getBaseUrl() { + return instance.baseUrl().toString(); + } + public RetryConfig getRetryConfig() { return retryConfig; } @@ -665,6 +751,30 @@ public Builder setHost(@NotNull String hostname) { return this; } + /** + * Configures the client to target a specific Contentstack region by resolving + * the correct Content Management API host from the bundled regions registry. + * + *

    This is a convenience alternative to calling {@link #setHost(String)} with + * a manually constructed hostname. + * + *

    {@code
    +         * Contentstack client = new Contentstack.Builder()
    +         *     .setAuthtoken("authtoken")
    +         *     .setRegion(ContentstackRegion.EU)
    +         *     .build();
    +         * }
    + * + * @param region region ID or alias (e.g. {@code "na"}, {@code "eu"}, {@code "azure-na"}). + * Use constants from {@link com.contentstack.cms.core.ContentstackRegion}. + * @return this Builder + * @throws IllegalArgumentException if the region is unknown or empty + */ + public Builder setRegion(@NotNull String region) { + this.hostname = Endpoint.getContentstackEndpoint(region, "contentManagement", true); + return this; + } + /** * Set port for client instance * diff --git a/src/main/java/com/contentstack/cms/core/Endpoint.java b/src/main/java/com/contentstack/cms/core/Endpoint.java new file mode 100644 index 00000000..1d5afabb --- /dev/null +++ b/src/main/java/com/contentstack/cms/core/Endpoint.java @@ -0,0 +1,238 @@ +package com.contentstack.cms.core; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Resolves Contentstack API endpoints for any region and service. + * + *

    Endpoint data is loaded from the bundled {@code regions.json} classpath resource. + * The parsed result is cached for the lifetime of the JVM process. + * If the bundled file is absent, a live download from + * {@code https://artifacts.contentstack.com/regions.json} is attempted as a fallback. + * + *

    {@code
    + * // Get a specific service URL
    + * String cdnUrl = Endpoint.getContentstackEndpoint("eu", "contentDelivery");
    + * // → "https://eu-cdn.contentstack.com"
    + *
    + * // Get the host without the https:// scheme (for use with Builder.setHost)
    + * String host = Endpoint.getContentstackEndpoint("eu", "contentDelivery", true);
    + * // → "eu-cdn.contentstack.com"
    + *
    + * // Get all endpoints for a region
    + * Map all = Endpoint.getContentstackEndpoints("eu");
    + * }
    + */ +public class Endpoint { + + private static final String REGIONS_URL = "https://artifacts.contentstack.com/regions.json"; + private static final String REGIONS_RESOURCE = "regions.json"; + + private static volatile JsonArray regionsData = null; + + private Endpoint() {} + + /** + * Returns the URL for a specific service in the given region. + * + * @param region canonical region ID ({@code na}, {@code eu}, {@code au}, {@code azure-na}, + * {@code azure-eu}, {@code gcp-na}, {@code gcp-eu}) or any accepted alias. + * Case-insensitive; {@code -} and {@code _} separators are equivalent. + * @param service service name (e.g. {@code contentManagement}, {@code contentDelivery}) + * @return full URL including {@code https://} + * @throws IllegalArgumentException if region or service is unknown, or region is empty + * @throws RuntimeException if {@code regions.json} cannot be loaded + */ + public static String getContentstackEndpoint(String region, String service) { + return getContentstackEndpoint(region, service, false); + } + + /** + * Returns the URL for a specific service in the given region. + * + * @param region canonical region ID or alias + * @param service service name + * @param omitHttps when {@code true}, strips {@code https://} from the result + * @return URL string, with or without scheme depending on {@code omitHttps} + * @throws IllegalArgumentException if region or service is unknown, or region is empty + * @throws RuntimeException if {@code regions.json} cannot be loaded + */ + public static String getContentstackEndpoint(String region, String service, boolean omitHttps) { + if (service == null || service.trim().isEmpty()) { + throw new IllegalArgumentException( + "Service must not be empty. Use getContentstackEndpoints(region) to retrieve all endpoints."); + } + JsonObject regionRow = resolveRegion(region); + JsonObject endpoints = regionRow.getAsJsonObject("endpoints"); + if (endpoints == null || !endpoints.has(service)) { + throw new IllegalArgumentException( + "Service \"" + service + "\" not found for region \"" + regionRow.get("id").getAsString() + "\""); + } + String url = endpoints.get(service).getAsString(); + return omitHttps ? stripHttps(url) : url; + } + + /** + * Returns all endpoint URLs for the given region as an ordered map. + * + * @param region canonical region ID or alias + * @return map of service name → URL (includes {@code https://}) + * @throws IllegalArgumentException if region is unknown or empty + * @throws RuntimeException if {@code regions.json} cannot be loaded + */ + public static Map getContentstackEndpoints(String region) { + return getContentstackEndpoints(region, false); + } + + /** + * Returns all endpoint URLs for the given region as an ordered map. + * + * @param region canonical region ID or alias + * @param omitHttps when {@code true}, strips {@code https://} from every URL + * @return map of service name → URL + * @throws IllegalArgumentException if region is unknown or empty + * @throws RuntimeException if {@code regions.json} cannot be loaded + */ + public static Map getContentstackEndpoints(String region, boolean omitHttps) { + JsonObject regionRow = resolveRegion(region); + JsonObject endpoints = regionRow.getAsJsonObject("endpoints"); + Map result = new LinkedHashMap<>(); + if (endpoints != null) { + for (Map.Entry entry : endpoints.entrySet()) { + String url = entry.getValue().getAsString(); + result.put(entry.getKey(), omitHttps ? stripHttps(url) : url); + } + } + return result; + } + + // ── internal ────────────────────────────────────────────────────────────── + + private static JsonObject resolveRegion(String region) { + if (region == null || region.trim().isEmpty()) { + throw new IllegalArgumentException("Empty region provided. Please provide a valid region."); + } + JsonArray regions = loadRegions(); + String normalized = region.trim().toLowerCase().replace('_', '-'); + + // First pass: exact match on canonical id + for (int i = 0; i < regions.size(); i++) { + JsonObject row = regions.get(i).getAsJsonObject(); + if (row.get("id").getAsString().equals(normalized)) { + return row; + } + } + + // Second pass: match on alias list (case-insensitive, normalised separators) + for (int i = 0; i < regions.size(); i++) { + JsonObject row = regions.get(i).getAsJsonObject(); + JsonArray aliases = row.getAsJsonArray("alias"); + if (aliases != null) { + for (int j = 0; j < aliases.size(); j++) { + String alias = aliases.get(j).getAsString().toLowerCase().replace('_', '-'); + if (alias.equals(normalized)) { + return row; + } + } + } + } + + throw new IllegalArgumentException("Invalid region: " + region); + } + + private static synchronized JsonArray loadRegions() { + if (regionsData != null) { + return regionsData; + } + + // Try bundled classpath resource first + InputStream is = Endpoint.class.getClassLoader().getResourceAsStream(REGIONS_RESOURCE); + if (is != null) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + String json = reader.lines().collect(Collectors.joining("\n")); + regionsData = JsonParser.parseString(json).getAsJsonObject().getAsJsonArray("regions"); + return regionsData; + } catch (Exception e) { + // fall through to live download + } + } + + // Fallback: download from Contentstack + try { + String json = downloadRegions(); + regionsData = JsonParser.parseString(json).getAsJsonObject().getAsJsonArray("regions"); + return regionsData; + } catch (Exception e) { + throw new RuntimeException( + "contentstack/cms: regions.json not found and could not be downloaded. " + + "Ensure the JAR was built correctly or network access is available.", e); + } + } + + private static String downloadRegions() throws IOException { + URL url = new URL(REGIONS_URL); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(10_000); + conn.setReadTimeout(10_000); + try (InputStream is = conn.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + return reader.lines().collect(Collectors.joining("\n")); + } finally { + conn.disconnect(); + } + } + + /** + * Forces a live download of the regions registry from + * {@code https://artifacts.contentstack.com/regions.json} and replaces + * the in-memory cache. + * + *

    Call this if you suspect new Contentstack regions or service URLs have + * been published since the SDK JAR was built, without needing to upgrade + * the SDK version. + * + *

    {@code
    +     * int count = Endpoint.refresh();
    +     * System.out.println("Loaded " + count + " regions from live registry.");
    +     * }
    + * + * @return the number of regions loaded from the live registry + * @throws RuntimeException if the download fails or the response is not valid JSON + */ + public static synchronized int refresh() { + try { + String json = downloadRegions(); + JsonArray fresh = JsonParser.parseString(json).getAsJsonObject().getAsJsonArray("regions"); + regionsData = fresh; + return fresh.size(); + } catch (Exception e) { + throw new RuntimeException( + "contentstack/cms: Failed to refresh regions.json from " + REGIONS_URL + ". " + + "Check network connectivity and try again.", e); + } + } + + private static String stripHttps(String url) { + return url.replaceAll("^https?://", ""); + } + + /** Clears the in-memory cache. For use in tests only. */ + static void resetCache() { + regionsData = null; + } +} diff --git a/src/test/java/com/contentstack/cms/TestClient.java b/src/test/java/com/contentstack/cms/TestClient.java index 2503e39b..e246501b 100644 --- a/src/test/java/com/contentstack/cms/TestClient.java +++ b/src/test/java/com/contentstack/cms/TestClient.java @@ -23,6 +23,7 @@ public class TestClient { : "managementToken99999999"; public final static String DEV_HOST = (env.get("dev_host") != null) ? env.get("dev_host").trim() : "api.contentstack.io"; + public final static String REGION = (env.get("region") != null) ? env.get("region").trim() : "na"; public final static String VARIANT_GROUP_UID = (env.get("variantGroupUid") != null) ? env.get("variantGroupUid") : "variantGroupUid99999999"; private static Contentstack instance; diff --git a/src/test/java/com/contentstack/cms/UnitTestSuite.java b/src/test/java/com/contentstack/cms/UnitTestSuite.java index 97df7a51..6c514051 100644 --- a/src/test/java/com/contentstack/cms/UnitTestSuite.java +++ b/src/test/java/com/contentstack/cms/UnitTestSuite.java @@ -1,6 +1,7 @@ package com.contentstack.cms; import com.contentstack.cms.core.AuthInterceptorTest; +import com.contentstack.cms.core.EndpointTest; import com.contentstack.cms.stack.EnvironmentUnitTest; import com.contentstack.cms.stack.GlobalFieldUnitTests; import com.contentstack.cms.stack.LocaleUnitTest; @@ -23,6 +24,7 @@ // Core tests AuthInterceptorTest.class, ContentstackUnitTest.class, + EndpointTest.class, // Stack module tests (only public classes) EnvironmentUnitTest.class, diff --git a/src/test/java/com/contentstack/cms/core/EndpointTest.java b/src/test/java/com/contentstack/cms/core/EndpointTest.java new file mode 100644 index 00000000..8584ef1c --- /dev/null +++ b/src/test/java/com/contentstack/cms/core/EndpointTest.java @@ -0,0 +1,411 @@ +package com.contentstack.cms.core; + +import com.contentstack.cms.Contentstack; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +public class EndpointTest { + + @BeforeEach + @AfterEach + void resetCache() { + Endpoint.resetCache(); + } + + // ── canonical IDs — contentDelivery ─────────────────────────────────────── + + @Test + void testNaContentDelivery() { + assertEquals("https://cdn.contentstack.io", + Endpoint.getContentstackEndpoint("na", "contentDelivery")); + } + + @Test + void testNaContentManagement() { + assertEquals("https://api.contentstack.io", + Endpoint.getContentstackEndpoint("na", "contentManagement")); + } + + @Test + void testEuContentDelivery() { + assertEquals("https://eu-cdn.contentstack.com", + Endpoint.getContentstackEndpoint("eu", "contentDelivery")); + } + + @Test + void testEuContentManagement() { + assertEquals("https://eu-api.contentstack.com", + Endpoint.getContentstackEndpoint("eu", "contentManagement")); + } + + @Test + void testAuContentDelivery() { + assertEquals("https://au-cdn.contentstack.com", + Endpoint.getContentstackEndpoint("au", "contentDelivery")); + } + + @Test + void testAzureNaContentDelivery() { + assertEquals("https://azure-na-cdn.contentstack.com", + Endpoint.getContentstackEndpoint("azure-na", "contentDelivery")); + } + + @Test + void testAzureEuContentDelivery() { + assertEquals("https://azure-eu-cdn.contentstack.com", + Endpoint.getContentstackEndpoint("azure-eu", "contentDelivery")); + } + + @Test + void testGcpNaContentDelivery() { + assertEquals("https://gcp-na-cdn.contentstack.com", + Endpoint.getContentstackEndpoint("gcp-na", "contentDelivery")); + } + + @Test + void testGcpEuContentDelivery() { + assertEquals("https://gcp-eu-cdn.contentstack.com", + Endpoint.getContentstackEndpoint("gcp-eu", "contentDelivery")); + } + + // ── region aliases resolve to the same endpoint ─────────────────────────── + + @ParameterizedTest + @ValueSource(strings = {"na", "us", "aws-na", "aws_na", "NA", "US", "AWS-NA", "AWS_NA"}) + void testNaAliasesAllResolveToNaCdn(String alias) { + assertEquals("https://cdn.contentstack.io", + Endpoint.getContentstackEndpoint(alias, "contentDelivery")); + } + + @ParameterizedTest + @ValueSource(strings = {"eu", "aws-eu", "aws_eu", "EU", "AWS-EU", "AWS_EU"}) + void testEuAliasesAllResolveToEuCdn(String alias) { + assertEquals("https://eu-cdn.contentstack.com", + Endpoint.getContentstackEndpoint(alias, "contentDelivery")); + } + + @ParameterizedTest + @ValueSource(strings = {"au", "aws-au", "aws_au", "AU", "AWS-AU", "AWS_AU"}) + void testAuAliasesAllResolveToAuCdn(String alias) { + assertEquals("https://au-cdn.contentstack.com", + Endpoint.getContentstackEndpoint(alias, "contentDelivery")); + } + + @ParameterizedTest + @ValueSource(strings = {"azure-na", "azure_na", "AZURE-NA", "AZURE_NA"}) + void testAzureNaAliasesResolve(String alias) { + assertEquals("https://azure-na-cdn.contentstack.com", + Endpoint.getContentstackEndpoint(alias, "contentDelivery")); + } + + @ParameterizedTest + @ValueSource(strings = {"azure-eu", "azure_eu", "AZURE-EU", "AZURE_EU"}) + void testAzureEuAliasesResolve(String alias) { + assertEquals("https://azure-eu-cdn.contentstack.com", + Endpoint.getContentstackEndpoint(alias, "contentDelivery")); + } + + @ParameterizedTest + @ValueSource(strings = {"gcp-na", "gcp_na", "GCP-NA", "GCP_NA"}) + void testGcpNaAliasesResolve(String alias) { + assertEquals("https://gcp-na-cdn.contentstack.com", + Endpoint.getContentstackEndpoint(alias, "contentDelivery")); + } + + @ParameterizedTest + @ValueSource(strings = {"gcp-eu", "gcp_eu", "GCP-EU", "GCP_EU"}) + void testGcpEuAliasesResolve(String alias) { + assertEquals("https://gcp-eu-cdn.contentstack.com", + Endpoint.getContentstackEndpoint(alias, "contentDelivery")); + } + + // ── omitHttps ───────────────────────────────────────────────────────────── + + @Test + void testOmitHttpsFalseKeepsScheme() { + String url = Endpoint.getContentstackEndpoint("na", "contentDelivery", false); + assertTrue(url.startsWith("https://")); + } + + @Test + void testOmitHttpsTrueStripsScheme() { + String host = Endpoint.getContentstackEndpoint("na", "contentDelivery", true); + assertFalse(host.startsWith("https://")); + assertEquals("cdn.contentstack.io", host); + } + + @Test + void testOmitHttpsEuCdn() { + assertEquals("eu-cdn.contentstack.com", + Endpoint.getContentstackEndpoint("eu", "contentDelivery", true)); + } + + @Test + void testOmitHttpsAzureNaApi() { + assertEquals("azure-na-api.contentstack.com", + Endpoint.getContentstackEndpoint("azure-na", "contentManagement", true)); + } + + @Test + void testOmitHttpsGcpEuCdn() { + assertEquals("gcp-eu-cdn.contentstack.com", + Endpoint.getContentstackEndpoint("gcp-eu", "contentDelivery", true)); + } + + // ── various service keys ─────────────────────────────────────────────────── + + @Test + void testNaAuth() { + assertEquals("https://auth-api.contentstack.com", + Endpoint.getContentstackEndpoint("na", "auth")); + } + + @Test + void testNaGraphqlDelivery() { + assertEquals("https://graphql.contentstack.com", + Endpoint.getContentstackEndpoint("na", "graphqlDelivery")); + } + + @Test + void testNaPreview() { + assertEquals("https://rest-preview.contentstack.com", + Endpoint.getContentstackEndpoint("na", "preview")); + } + + @Test + void testNaApplication() { + assertEquals("https://app.contentstack.com", + Endpoint.getContentstackEndpoint("na", "application")); + } + + @Test + void testNaAssetManagement() { + assertEquals("https://am-api.contentstack.com", + Endpoint.getContentstackEndpoint("na", "assetManagement")); + } + + @Test + void testNaDeveloperHub() { + assertEquals("https://developerhub-api.contentstack.com", + Endpoint.getContentstackEndpoint("na", "developerHub")); + } + + @Test + void testEuAutomate() { + assertEquals("https://eu-prod-automations-api.contentstack.com", + Endpoint.getContentstackEndpoint("eu", "automate")); + } + + @Test + void testNaLaunch() { + assertEquals("https://launch-api.contentstack.com", + Endpoint.getContentstackEndpoint("na", "launch")); + } + + // ── all-endpoints map ────────────────────────────────────────────────────── + + @Test + void testGetAllEndpointsForNaNotEmpty() { + Map endpoints = Endpoint.getContentstackEndpoints("na"); + assertNotNull(endpoints); + assertFalse(endpoints.isEmpty()); + } + + @Test + void testGetAllEndpointsForNaContainsExpectedKeys() { + Map endpoints = Endpoint.getContentstackEndpoints("na"); + assertEquals("https://cdn.contentstack.io", endpoints.get("contentDelivery")); + assertEquals("https://api.contentstack.io", endpoints.get("contentManagement")); + assertEquals("https://auth-api.contentstack.com", endpoints.get("auth")); + } + + @Test + void testGetAllEndpointsForEu() { + Map endpoints = Endpoint.getContentstackEndpoints("eu"); + assertEquals("https://eu-cdn.contentstack.com", endpoints.get("contentDelivery")); + assertEquals("https://eu-api.contentstack.com", endpoints.get("contentManagement")); + } + + @Test + void testGetAllEndpointsOmitHttps() { + Map hosts = Endpoint.getContentstackEndpoints("eu", true); + assertEquals("eu-cdn.contentstack.com", hosts.get("contentDelivery")); + assertEquals("eu-api.contentstack.com", hosts.get("contentManagement")); + hosts.values().forEach(v -> assertFalse(v.startsWith("https://"), + "Expected no scheme but got: " + v)); + } + + @Test + void testGetAllEndpointsWithSchemePresentByDefault() { + Map endpoints = Endpoint.getContentstackEndpoints("na"); + endpoints.values().forEach(v -> assertTrue(v.startsWith("https://"), + "Expected https:// but got: " + v)); + } + + // ── error handling ──────────────────────────────────────────────────────── + + @Test + void testEmptyRegionThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> Endpoint.getContentstackEndpoint("", "contentDelivery")); + assertTrue(ex.getMessage().contains("Empty region")); + } + + @Test + void testNullRegionThrows() { + assertThrows(IllegalArgumentException.class, + () -> Endpoint.getContentstackEndpoint(null, "contentDelivery")); + } + + @Test + void testInvalidRegionThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> Endpoint.getContentstackEndpoint("asia-pacific", "contentDelivery")); + assertTrue(ex.getMessage().contains("Invalid region")); + assertTrue(ex.getMessage().contains("asia-pacific")); + } + + @Test + void testUnknownServiceThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> Endpoint.getContentstackEndpoint("na", "cms")); + assertTrue(ex.getMessage().contains("cms")); + assertTrue(ex.getMessage().contains("na")); + } + + @Test + void testEmptyServiceThrows() { + assertThrows(IllegalArgumentException.class, + () -> Endpoint.getContentstackEndpoint("na", "")); + } + + @Test + void testNullServiceThrows() { + assertThrows(IllegalArgumentException.class, + () -> Endpoint.getContentstackEndpoint("na", null)); + } + + // ── Contentstack class proxy ─────────────────────────────────────────────── + + @Test + void testContentstackProxyWithScheme() { + assertEquals("https://eu-api.contentstack.com", + Contentstack.getContentstackEndpoint("eu", "contentManagement")); + } + + @Test + void testContentstackProxyOmitHttps() { + assertEquals("eu-api.contentstack.com", + Contentstack.getContentstackEndpoint("eu", "contentManagement", true)); + } + + // ── Builder.setRegion() ──────────────────────────────────────────────────── + + @Test + void testBuilderSetRegionEuResolvesCorrectHost() { + Contentstack client = new Contentstack.Builder() + .setRegion("eu") + .build(); + assertEquals("eu-api.contentstack.com", client.getHost()); + } + + @Test + void testBuilderSetRegionNaResolvesCorrectHost() { + Contentstack client = new Contentstack.Builder() + .setRegion("na") + .build(); + assertEquals("api.contentstack.io", client.getHost()); + } + + @Test + void testBuilderSetRegionAzureNaResolvesCorrectHost() { + Contentstack client = new Contentstack.Builder() + .setRegion("azure-na") + .build(); + assertEquals("azure-na-api.contentstack.com", client.getHost()); + } + + @Test + void testBuilderSetRegionGcpEuResolvesCorrectHost() { + Contentstack client = new Contentstack.Builder() + .setRegion("gcp-eu") + .build(); + assertEquals("gcp-eu-api.contentstack.com", client.getHost()); + } + + @Test + void testBuilderSetRegionInvalidThrows() { + assertThrows(IllegalArgumentException.class, + () -> new Contentstack.Builder().setRegion("invalid-region")); + } + + @Test + void testBuilderSetRegionWithAuthtoken() { + Contentstack client = new Contentstack.Builder() + .setAuthtoken("fake_authtoken") + .setRegion("au") + .build(); + assertEquals("au-api.contentstack.com", client.getHost()); + } + + // ── caching — second call returns same result without re-parsing ─────────── + + @Test + void testCachingReturnsSameResult() { + String first = Endpoint.getContentstackEndpoint("na", "contentDelivery"); + String second = Endpoint.getContentstackEndpoint("na", "contentDelivery"); + assertEquals(first, second); + } + + // ── refresh() — forces live download and replaces cache ─────────────────── + + @Test + void testRefreshReturnsPositiveRegionCount() { + int count = Endpoint.refresh(); + assertTrue(count > 0, "refresh() should return at least one region"); + } + + @Test + void testRefreshLoadsAllKnownRegions() { + int count = Endpoint.refresh(); + assertEquals(7, count, "Expected 7 regions from the live registry"); + } + + @Test + void testRefreshUpdatesCache() { + // prime the cache from the bundled file + Endpoint.getContentstackEndpoint("na", "contentDelivery"); + + // force refresh from live + Endpoint.refresh(); + + // subsequent resolution should still work correctly + assertEquals("https://cdn.contentstack.io", + Endpoint.getContentstackEndpoint("na", "contentDelivery")); + } + + @Test + void testContentstackRefreshRegionsProxy() { + int count = Contentstack.refreshRegions(); + assertTrue(count > 0); + } + + @Test + void testRefreshThenResolveAllRegions() { + Endpoint.refresh(); + // verify all 7 canonical region IDs still resolve after a live refresh + String[] regions = {"na", "eu", "au", "azure-na", "azure-eu", "gcp-na", "gcp-eu"}; + for (String region : regions) { + assertDoesNotThrow(() -> Endpoint.getContentstackEndpoint(region, "contentManagement"), + "Should resolve contentManagement for region: " + region); + } + } +} diff --git a/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java b/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java index 4b027193..b6da2582 100644 --- a/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java +++ b/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java @@ -8,7 +8,8 @@ @SuppressWarnings("deprecation") @RunWith(JUnitPlatform.class) @SelectClasses({ - TaxonomyAPITest.class, + EndpointAPITest.class, + TaxonomyAPITest.class, AssetAPITest.class, ContentTypeAPITest.class, EntryFieldsAPITest.class, diff --git a/src/test/java/com/contentstack/cms/stack/EndpointAPITest.java b/src/test/java/com/contentstack/cms/stack/EndpointAPITest.java new file mode 100644 index 00000000..f5498475 --- /dev/null +++ b/src/test/java/com/contentstack/cms/stack/EndpointAPITest.java @@ -0,0 +1,212 @@ +package com.contentstack.cms.stack; + +import com.contentstack.cms.Contentstack; +import com.contentstack.cms.TestClient; +import com.contentstack.cms.core.Endpoint; +import com.contentstack.cms.core.Util; +import okhttp3.Request; +import org.junit.jupiter.api.*; +import retrofit2.Response; + +import java.io.IOException; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * API-level tests for the endpoint integration feature. + * + *

    URL-structure tests verify the resolved host is wired into every outgoing + * request. Live-call tests make real network requests to the NA management API + * using credentials from {@code .env}. + */ +@Tag("api") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class EndpointAPITest { + + private static final String API_KEY = TestClient.API_KEY; + private static final String MANAGEMENT_TOKEN = TestClient.MANAGEMENT_TOKEN; + private static final String REGION = TestClient.REGION; + + // ── URL structure — all 7 regions ──────────────────────────────────────── + + @Order(1) + @Test + void testNaRegionRequestUrlIsCorrect() { + Stack stack = clientForRegion("na").stack(API_KEY, MANAGEMENT_TOKEN); + Request req = stack.contentType("test").find().request(); + assertTrue(req.url().isHttps()); + assertEquals("api.contentstack.io", req.url().host()); + assertEquals("v3", req.url().pathSegments().get(0)); + } + + @Order(2) + @Test + void testEuRegionRequestUrlIsCorrect() { + Stack stack = clientForRegion("eu").stack(API_KEY, MANAGEMENT_TOKEN); + Request req = stack.contentType("test").find().request(); + assertTrue(req.url().isHttps()); + assertEquals("eu-api.contentstack.com", req.url().host()); + } + + @Order(3) + @Test + void testAuRegionRequestUrlIsCorrect() { + Stack stack = clientForRegion("au").stack(API_KEY, MANAGEMENT_TOKEN); + Request req = stack.contentType("test").find().request(); + assertTrue(req.url().isHttps()); + assertEquals("au-api.contentstack.com", req.url().host()); + } + + @Order(4) + @Test + void testAzureNaRegionRequestUrlIsCorrect() { + Stack stack = clientForRegion("azure-na").stack(API_KEY, MANAGEMENT_TOKEN); + Request req = stack.contentType("test").find().request(); + assertTrue(req.url().isHttps()); + assertEquals("azure-na-api.contentstack.com", req.url().host()); + } + + @Order(5) + @Test + void testAzureEuRegionRequestUrlIsCorrect() { + Stack stack = clientForRegion("azure-eu").stack(API_KEY, MANAGEMENT_TOKEN); + Request req = stack.contentType("test").find().request(); + assertTrue(req.url().isHttps()); + assertEquals("azure-eu-api.contentstack.com", req.url().host()); + } + + @Order(6) + @Test + void testGcpNaRegionRequestUrlIsCorrect() { + Stack stack = clientForRegion("gcp-na").stack(API_KEY, MANAGEMENT_TOKEN); + Request req = stack.contentType("test").find().request(); + assertTrue(req.url().isHttps()); + assertEquals("gcp-na-api.contentstack.com", req.url().host()); + } + + @Order(7) + @Test + void testGcpEuRegionRequestUrlIsCorrect() { + Stack stack = clientForRegion("gcp-eu").stack(API_KEY, MANAGEMENT_TOKEN); + Request req = stack.contentType("test").find().request(); + assertTrue(req.url().isHttps()); + assertEquals("gcp-eu-api.contentstack.com", req.url().host()); + } + + // ── region aliases are wired correctly ──────────────────────────────────── + + @Order(8) + @Test + void testUsAliasResolvesToNaHost() { + Stack stack = clientForRegion("us").stack(API_KEY, MANAGEMENT_TOKEN); + assertEquals("api.contentstack.io", stack.contentType("t").find().request().url().host()); + } + + @Order(9) + @Test + void testAwsEuAliasResolvesToEuHost() { + Stack stack = clientForRegion("aws-eu").stack(API_KEY, MANAGEMENT_TOKEN); + assertEquals("eu-api.contentstack.com", stack.contentType("t").find().request().url().host()); + } + + @Order(10) + @Test + void testAzureNaUnderscoreAliasResolvesCorrectly() { + Stack stack = clientForRegion("azure_na").stack(API_KEY, MANAGEMENT_TOKEN); + assertEquals("azure-na-api.contentstack.com", stack.contentType("t").find().request().url().host()); + } + + @Order(11) + @Test + void testUppercaseRegionAliasResolvesCorrectly() { + Stack stack = clientForRegion("GCP-EU").stack(API_KEY, MANAGEMENT_TOKEN); + assertEquals("gcp-eu-api.contentstack.com", stack.contentType("t").find().request().url().host()); + } + + // ── setRegion wires host into Retrofit base URL ─────────────────────────── + + @Order(12) + @Test + void testSetRegionWiresCorrectBaseUrl() { + Contentstack client = new Contentstack.Builder() + .setRegion("eu") + .build(); + assertTrue(client.getBaseUrl().contains("eu-api.contentstack.com"), + "Base URL should contain EU CMA host"); + } + + @Order(13) + @Test + void testSetRegionNaWiresDefaultHost() { + Contentstack client = new Contentstack.Builder() + .setRegion("na") + .build(); + assertEquals("api.contentstack.io", client.getHost()); + assertTrue(client.getBaseUrl().contains("api.contentstack.io")); + } + + // ── getContentstackEndpoints() returns full map ─────────────────────────── + + @Order(14) + @Test + void testGetAllEndpointsForRegionNotEmpty() { + Map endpoints = Contentstack.getContentstackEndpoints(REGION); + assertNotNull(endpoints); + assertFalse(endpoints.isEmpty()); + assertTrue(endpoints.containsKey("contentManagement")); + assertTrue(endpoints.containsKey("contentDelivery")); + assertTrue(endpoints.containsKey("auth")); + } + + @Order(15) + @Test + void testGetAllEndpointsOmitHttps() { + Map hosts = Contentstack.getContentstackEndpoints("eu", true); + hosts.values().forEach(v -> + assertFalse(v.startsWith("https://"), "Expected no scheme but got: " + v)); + assertEquals("eu-api.contentstack.com", hosts.get("contentManagement")); + } + + // ── live API call — uses env region + credentials ───────────────────────── + + @Order(16) + @Test + void testLiveCallWithSetRegionReturnsResponse() throws IOException { + Contentstack client = new Contentstack.Builder() + .setAuthtoken(TestClient.AUTHTOKEN) + .setRegion(REGION) + .build(); + + String resolvedHost = Endpoint.getContentstackEndpoint(REGION, "contentManagement", true); + assertEquals(resolvedHost, client.getHost()); + + Response response = client.user().getUser().execute(); + // 200 = valid authtoken, 401 = invalid but network + endpoint resolution worked + assertTrue(response.code() == 200 || response.code() == 401, + "Expected 200 or 401, got: " + response.code()); + } + + @Order(17) + @Test + void testLiveStackCallWithSetRegion() throws IOException { + Stack stack = new Contentstack.Builder() + .setAuthtoken(TestClient.AUTHTOKEN) + .setRegion(REGION) + .build() + .stack(API_KEY, MANAGEMENT_TOKEN); + + Request req = stack.contentType("").find().request(); + String expectedHost = Endpoint.getContentstackEndpoint(REGION, "contentManagement", true); + assertEquals(expectedHost, req.url().host()); + assertTrue(req.url().isHttps()); + assertEquals(Util.VERSION, req.url().pathSegments().get(0)); + } + + // ── helper ──────────────────────────────────────────────────────────────── + + private Contentstack clientForRegion(String region) { + return new Contentstack.Builder().setRegion(region).build(); + } +} From 537e51cee7ca570ba4fa527408878ade8ed20c37 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 8 Jun 2026 10:56:44 +0530 Subject: [PATCH 189/197] feat: Update changelog and improve region parameter documentation in Contentstack.Builder --- changelog.md | 7 +------ src/main/java/com/contentstack/cms/Contentstack.java | 6 +++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/changelog.md b/changelog.md index d6e917fd..cc612d9d 100644 --- a/changelog.md +++ b/changelog.md @@ -4,12 +4,7 @@ ### Jun 05, 2026 -- Feature: Dynamic endpoint resolution via `Endpoint.getContentstackEndpoint()` backed by the Contentstack Regions Registry (`https://artifacts.contentstack.com/regions.json`). Resolves the correct API URL for any of the 7 supported regions (NA, EU, AU, Azure NA, Azure EU, GCP NA, GCP EU) and 18 service keys without hardcoding host strings. -- Feature: `Contentstack.Builder.setRegion(String)` — convenience method to target a region directly (e.g. `.setRegion("eu")`), automatically resolving the correct Content Management API host. -- Feature: `Contentstack.getContentstackEndpoint(region, service)` and `Contentstack.getContentstackEndpoints(region)` static proxy methods for ad-hoc URL resolution. -- Feature: `Contentstack.refreshRegions()` — forces a live download of the regions registry and refreshes the in-memory cache, so newly published regions or service URLs are available without upgrading the SDK. -- Feature: `regions.json` bundled in the JAR and auto-refreshed at build time via `scripts/download-regions.sh` (invoked on the Maven `generate-resources` phase). -- Feature: `Contentstack.getHost()` and `Contentstack.getBaseUrl()` public accessors on the client instance. +- Feature: Dynamic endpoint resolution via `Endpoint.getContentstackEndpoint()` and `Builder.setRegion()` backed by the Contentstack Regions Registry. ## v1.11.2 diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 2603b760..69295d04 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -761,12 +761,12 @@ public Builder setHost(@NotNull String hostname) { *

    {@code
              * Contentstack client = new Contentstack.Builder()
              *     .setAuthtoken("authtoken")
    -         *     .setRegion(ContentstackRegion.EU)
    +         *     .setRegion("eu")
              *     .build();
              * }
    * - * @param region region ID or alias (e.g. {@code "na"}, {@code "eu"}, {@code "azure-na"}). - * Use constants from {@link com.contentstack.cms.core.ContentstackRegion}. + * @param region region ID or alias (e.g. {@code "na"}, {@code "eu"}, {@code "azure-na"}, + * {@code "azure-eu"}, {@code "gcp-na"}, {@code "gcp-eu"}, {@code "au"}). * @return this Builder * @throws IllegalArgumentException if the region is unknown or empty */ From be33d735b95821f29b25ec5377cc91e4842397de Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Mon, 8 Jun 2026 15:20:09 +0530 Subject: [PATCH 190/197] fix: update release date for v1.12.0 in changelog --- changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index cc612d9d..5f5c3c64 100644 --- a/changelog.md +++ b/changelog.md @@ -2,7 +2,7 @@ ## v1.12.0 -### Jun 05, 2026 +### Jun 15, 2026 - Feature: Dynamic endpoint resolution via `Endpoint.getContentstackEndpoint()` and `Builder.setRegion()` backed by the Contentstack Regions Registry. From d98762d7390c6f2ed55fef50de66414e5e917d7c Mon Sep 17 00:00:00 2001 From: Aravind Kumar Date: Fri, 19 Jun 2026 12:57:52 +0530 Subject: [PATCH 191/197] issues-jira.yml --- .github/workflows/issues-jira.yml | 121 +++++++++++++++++++++++++----- 1 file changed, 104 insertions(+), 17 deletions(-) diff --git a/.github/workflows/issues-jira.yml b/.github/workflows/issues-jira.yml index 7bf04694..5f65d0b6 100644 --- a/.github/workflows/issues-jira.yml +++ b/.github/workflows/issues-jira.yml @@ -2,30 +2,117 @@ name: Create Jira Ticket for Github Issue on: issues: - types: [opened] + types: [opened, reopened] jobs: issue-jira: runs-on: ubuntu-latest steps: + - name: Create Jira Issue + id: create_jira + uses: actions/github-script@v9 + with: + script: | + const baseUrl = process.env.JIRA_BASE_URL; + const userEmail = process.env.JIRA_USER_EMAIL; + const jiraToken = process.env.JIRA_API_TOKEN; + const jiraProject = process.env.JIRA_PROJECT; + const jiraIssueType = process.env.JIRA_ISSUE_TYPE; + const jiraFields = JSON.parse(process.env.ISSUES_JIRA_FIELDS); + + let requestBody = JSON.stringify({ + fields: { + ...jiraFields, + "project": { + "key": jiraProject + }, + "issuetype": { + "name": jiraIssueType + }, + "summary": "Github | Issue | ${{ github.event.repository.name }} | ${{ github.event.issue.title }}", + "description": { + "version": 1, + "type": "doc", + "content": [ + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Github Issue", + "marks": [ + { + "type": "strong" + } + ] + }, + { + "type": "text", + "text": ": " + }, + { + "type": "text", + "text": "${{ github.event.issue.html_url }}", + "marks": [ + { + "type": "link", + "attrs": { + "href": "${{ github.event.issue.html_url }}" + } + } + ] + } + ] + }, + { + "type": "paragraph", + "content": [ + { + "type": "text", + "text": "Description", + "marks": [ + { + "type": "strong" + } + ] + }, + { + "type": "text", + "text": ":" + } + ] + }, + { + "type": "codeBlock", + "content": [ + { + "type": "text", + "text": `${{ github.event.issue.body }}` + } + ] + } + ] + } + } + }); - - name: Login to Jira - uses: atlassian/gajira-login@master + const response = await fetch(`${baseUrl}/rest/api/3/issue`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Basic ${btoa(userEmail + ":" + jiraToken)}` + }, + body: requestBody + }); + if (!response.ok) { + throw new Error(`JIRA API error! Status: ${response.status}`); + } + const data = await response.json(); + console.log('Jira Issue Created:', data.key); env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - - - name: Create Jira Issue - id: create_jira - uses: atlassian/gajira-create@master - with: - project: ${{ secrets.JIRA_PROJECT }} - issuetype: ${{ secrets.JIRA_ISSUE_TYPE }} - summary: Github | Issue | ${{ github.event.repository.name }} | ${{ github.event.issue.title }} - description: | - *GitHub Issue:* ${{ github.event.issue.html_url }} - - *Description:* - ${{ github.event.issue.body }} - fields: "${{ secrets.ISSUES_JIRA_FIELDS }}" \ No newline at end of file + JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }} + JIRA_ISSUE_TYPE: ${{ secrets.JIRA_ISSUE_TYPE }} + ISSUES_JIRA_FIELDS: "${{ secrets.ISSUES_JIRA_FIELDS }}" From 61dd73b158750932f0fb076bd5b4db56de44b956 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 24 Jun 2026 14:26:01 +0530 Subject: [PATCH 192/197] feat: add asset scanning tests and update header assertions in AssetUnitTest and AssetAPITest --- .../com/contentstack/cms/UnitTestSuite.java | 2 + .../contentstack/cms/stack/AssetAPITest.java | 314 +++++++++++++++--- .../contentstack/cms/stack/AssetUnitTest.java | 125 ++++++- 3 files changed, 377 insertions(+), 64 deletions(-) diff --git a/src/test/java/com/contentstack/cms/UnitTestSuite.java b/src/test/java/com/contentstack/cms/UnitTestSuite.java index 6c514051..9a54d743 100644 --- a/src/test/java/com/contentstack/cms/UnitTestSuite.java +++ b/src/test/java/com/contentstack/cms/UnitTestSuite.java @@ -2,6 +2,7 @@ import com.contentstack.cms.core.AuthInterceptorTest; import com.contentstack.cms.core.EndpointTest; +import com.contentstack.cms.stack.AssetUnitTest; import com.contentstack.cms.stack.EnvironmentUnitTest; import com.contentstack.cms.stack.GlobalFieldUnitTests; import com.contentstack.cms.stack.LocaleUnitTest; @@ -27,6 +28,7 @@ EndpointTest.class, // Stack module tests (only public classes) + AssetUnitTest.class, EnvironmentUnitTest.class, GlobalFieldUnitTests.class, LocaleUnitTest.class, diff --git a/src/test/java/com/contentstack/cms/stack/AssetAPITest.java b/src/test/java/com/contentstack/cms/stack/AssetAPITest.java index e1090709..34da4a33 100644 --- a/src/test/java/com/contentstack/cms/stack/AssetAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/AssetAPITest.java @@ -5,18 +5,14 @@ import okhttp3.Request; import okhttp3.ResponseBody; import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; import org.junit.jupiter.api.*; import retrofit2.Response; import java.io.IOException; import java.util.HashMap; import java.util.Objects; -import com.contentstack.cms.stack.FileUploader; -import org.junit.jupiter.api.Test; -import java.io.File; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; @Tag("API") class AssetAPITest { @@ -51,7 +47,8 @@ void testFindAssets() { Assertions.assertEquals(3, request.headers().size()); Assertions.assertTrue(request.isHttps(), "always works on https"); Assertions.assertEquals("GET", request.method(), "works with GET call"); - Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, request.url().port(), "port should be 443"); + Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); + Assertions.assertEquals(443, request.url().port(), "port should be 443"); Assertions.assertTrue(request.url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(request.url().pathSegments().contains("assets"), "url segment should contain assets"); Assertions.assertFalse(Objects.requireNonNull(request.url().query()).isEmpty(), @@ -68,66 +65,59 @@ void testFetch() { asset.addParam("include_publish_details", true); asset.addParam("environment", "production"); asset.addParam("relative_urls", false); - - // Headers api_key(required) & authorization(required) asset.addHeader("api_key", API_KEY); asset.addHeader("authorization", MANAGEMENT_TOKEN); asset.addHeader("authtoken", AUTHTOKEN); - // Create Asset Instance to find all assets Request request = asset.fetch().request(); Assertions.assertEquals(3, request.headers().size()); Assertions.assertTrue(request.isHttps(), "always works on https"); Assertions.assertEquals("GET", request.method(), "works with GET call"); - Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, request.url().port(), "port should be 443"); + Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); + Assertions.assertEquals(443, request.url().port(), "port should be 443"); Assertions.assertTrue(request.url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(request.url().pathSegments().contains("assets"), "url segment should contain assets"); Assertions.assertFalse(Objects.requireNonNull(request.url().query()).isEmpty(), "query params should not be empty"); - } @Order(3) @Test void testGetAssetByFolderUid() throws IOException { asset.clearParams(); - // Headers api_key(required) & authorization(required) asset.addHeader("api_key", API_KEY); asset.addHeader("authorization", MANAGEMENT_TOKEN); - // Create Asset Instance to find all assets Response resp = asset.byFolderUid(_uid).execute(); Assertions.assertEquals(5, resp.raw().request().headers().size()); Assertions.assertTrue(resp.raw().request().headers().names().contains("api_key")); Assertions.assertTrue(resp.raw().request().headers().names().contains("authorization")); Assertions.assertTrue(resp.raw().request().isHttps(), "always works on https"); Assertions.assertEquals("GET", resp.raw().request().method(), "works with GET call"); - Assertions.assertEquals("https", resp.raw().request().url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, resp.raw().request().url().port(), "port should be 443"); + Assertions.assertEquals("https", resp.raw().request().url().scheme(), "the scheme should be https"); + Assertions.assertEquals(443, resp.raw().request().url().port(), "port should be 443"); Assertions.assertTrue(resp.raw().request().url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(resp.raw().request().url().pathSegments().contains("assets"), "url segment should contain assets"); Assertions.assertFalse(Objects.requireNonNull(resp.raw().request().url().query()).isEmpty(), "query params should not be empty"); - } @Test void testAssetSubFolder() throws IOException { asset.clearParams(); - // Headers api_key(required) & authorization(required) asset.addHeader("api_key", API_KEY); asset.addHeader("authtoken", AUTHTOKEN); - // Create Asset Instance to find all assets Request request = asset.subfolder(_uid, true).request(); Assertions.assertTrue(request.headers().names().contains("api_key")); Assertions.assertTrue(request.headers().names().contains("authtoken")); Assertions.assertTrue(request.isHttps(), "always works on https"); Assertions.assertEquals("GET", request.method(), "works with GET call"); - Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, request.url().port(), "port should be 443"); + Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); + Assertions.assertEquals(443, request.url().port(), "port should be 443"); Assertions.assertTrue(request.url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(request.url().pathSegments().contains("assets"), "url segment should contain assets"); Assertions.assertFalse(Objects.requireNonNull(request.url().query()).isEmpty(), "query params should not be empty"); - } @Test @@ -142,18 +132,17 @@ void testAssetUpload() { String filePath = "/Users/shaileshmishra/Desktop/image.jpeg"; String description = "The calender has been placed to assets by shaileshmishra"; Request request = asset.uploadAsset(filePath, description).request(); - // The assertions Assertions.assertEquals(3, request.headers().size()); Assertions.assertTrue(request.headers().names().contains("api_key")); Assertions.assertTrue(request.headers().names().contains("authtoken")); Assertions.assertTrue(request.isHttps(), "always works on https"); Assertions.assertEquals("POST", request.method(), "works with GET call"); - Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, request.url().port(), "port should be 443"); + Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); + Assertions.assertEquals(443, request.url().port(), "port should be 443"); Assertions.assertTrue(request.url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(request.url().pathSegments().contains("assets"), "url segment should contain assets"); Assertions.assertFalse(Objects.requireNonNull(request.url().query()).isEmpty(), "query params should not be empty"); - } @Test @@ -161,30 +150,26 @@ void testAssetUpload() { void testAssetReplace() throws IOException { asset.clearParams(); asset = client.stack().asset(_uid); - asset.addParam("include_branch", true); asset.addParam("relative_urls", false); asset.addParam("environment", "production"); - // Headers api_key(required) & authorization(required) asset.addHeader("api_key", API_KEY); asset.addHeader("authorization", MANAGEMENT_TOKEN); - // Create Asset Instance to find all assets String filePath = "/Users/shaileshmishra/Downloads/calendar.png"; Response resp = asset.replace(filePath, "Assets created by ***REMOVED***").execute(); - // The assertions Assertions.assertEquals(6, resp.raw().request().headers().size()); Assertions.assertTrue(resp.raw().request().headers().names().contains("api_key")); Assertions.assertTrue(resp.raw().request().headers().names().contains("authtoken")); Assertions.assertTrue(resp.raw().request().isHttps(), "always works on https"); Assertions.assertEquals("PUT", resp.raw().request().method(), "works with GET call"); - Assertions.assertEquals("https", resp.raw().request().url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, resp.raw().request().url().port(), "port should be 443"); + Assertions.assertEquals("https", resp.raw().request().url().scheme(), "the scheme should be https"); + Assertions.assertEquals(443, resp.raw().request().url().port(), "port should be 443"); Assertions.assertTrue(resp.raw().request().url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(resp.raw().request().url().pathSegments().contains("assets"), "url segment should contain assets"); Assertions.assertFalse(Objects.requireNonNull(resp.raw().request().url().query()).isEmpty(), "query params should not be empty"); - } @Test @@ -198,13 +183,13 @@ void testAssetGeneratePermanentUrl() throws IOException { subBody.put("permanent_url", "https://api.contentstack.io/v3/assets/stack_api_key/asset_UID/sample-slug.jpeg"); body.put("asset", body); Response resp = asset.generatePermanentUrl(body).execute(); - // The assertions Assertions.assertEquals(6, resp.raw().request().headers().size()); Assertions.assertTrue(resp.raw().request().headers().names().contains("api_key")); Assertions.assertTrue(resp.raw().request().headers().names().contains("authtoken")); Assertions.assertTrue(resp.raw().request().isHttps(), "always works on https"); Assertions.assertEquals("PUT", resp.raw().request().method(), "works with GET call"); - Assertions.assertEquals("https", resp.raw().request().url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, resp.raw().request().url().port(), "port should be 443"); + Assertions.assertEquals("https", resp.raw().request().url().scheme(), "the scheme should be https"); + Assertions.assertEquals(443, resp.raw().request().url().port(), "port should be 443"); Assertions.assertTrue(resp.raw().request().url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(resp.raw().request().url().pathSegments().contains("assets"), @@ -216,24 +201,21 @@ void testAssetGeneratePermanentUrl() throws IOException { @Test void testAssetDownloadPermanentUrl() throws IOException { asset = client.stack().asset(_uid); - // Headers api_key(required) & authorization(required) asset.addHeader("api_key", API_KEY); asset.addHeader("authorization", MANAGEMENT_TOKEN); asset.addHeader("authtoken", AUTHTOKEN); - // Create Asset Instance to find all assets Request request = asset.getPermanentUrl("www.google.com/search").request(); - // The assertions Assertions.assertEquals(3, request.headers().size()); Assertions.assertTrue(request.headers().names().contains("api_key")); Assertions.assertTrue(request.headers().names().contains("authtoken")); Assertions.assertTrue(request.isHttps(), "always works on https"); Assertions.assertEquals("GET", request.method(), "works with GET call"); - Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); Assertions.assertEquals(443, request.url().port(), "port should be 443"); + Assertions.assertEquals("https", request.url().scheme(), "the scheme should be https"); + Assertions.assertEquals(443, request.url().port(), "port should be 443"); Assertions.assertTrue(request.url().pathSegments().contains("v3"), "the first segment of url should be v3"); Assertions.assertTrue(request.url().pathSegments().contains("assets"), "url segment should contain assets"); } - @Test void testAssetUploadWithMultipleParams() throws IOException { String description = "The calender has been placed to assets by ***REMOVED***"; @@ -259,36 +241,44 @@ void testFetchSingleAssetPojo() throws IOException { .addParam("include_count", true); Request request = asset.fetchAsPojo().request(); Assertions.assertEquals("GET", request.method()); - Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals(443, request.url().port()); + Assertions.assertEquals("https", request.url().scheme()); + Assertions.assertEquals(443, request.url().port()); Assertions.assertTrue(request.url().pathSegments().contains("v3")); - Assertions.assertTrue(request.url().pathSegments().contains("assets")); } + Assertions.assertTrue(request.url().pathSegments().contains("assets")); + } @Test void testFetchAllAssetsPojo() throws IOException { asset = client.stack().asset(); Request request = asset.findAsPojo().request(); Assertions.assertEquals("GET", request.method()); - Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals(443, request.url().port()); + Assertions.assertEquals("https", request.url().scheme()); + Assertions.assertEquals(443, request.url().port()); Assertions.assertTrue(request.url().pathSegments().contains("v3")); - Assertions.assertTrue(request.url().pathSegments().contains("assets")); } + Assertions.assertTrue(request.url().pathSegments().contains("assets")); + } @Test void testFetchAssetsByFolderUidPojo() throws IOException { asset = client.stack().asset(); Request request = asset.byFolderUidAsPojo("folder_uid").request(); Assertions.assertEquals("GET", request.method()); - Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals(443, request.url().port()); + Assertions.assertEquals("https", request.url().scheme()); + Assertions.assertEquals(443, request.url().port()); Assertions.assertTrue(request.url().pathSegments().contains("v3")); - Assertions.assertTrue(request.url().pathSegments().contains("assets")); } + Assertions.assertTrue(request.url().pathSegments().contains("assets")); + } @Test void testFetchAssetsBySubFolderUidPojo() throws IOException { asset = client.stack().asset(); Request request = asset.subfolderAsPojo("subfolder_uid", true).request(); Assertions.assertEquals("GET", request.method()); - Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals(443, request.url().port()); + Assertions.assertEquals("https", request.url().scheme()); + Assertions.assertEquals(443, request.url().port()); Assertions.assertTrue(request.url().pathSegments().contains("v3")); - Assertions.assertTrue(request.url().pathSegments().contains("assets")); } + Assertions.assertTrue(request.url().pathSegments().contains("assets")); + } @Test void testFetchSingleFolderByNamePojo() { @@ -299,25 +289,245 @@ void testFetchSingleFolderByNamePojo() { asset.addParam("query", queryContent); Request request = asset.getSingleFolderByNameAsPojo().request(); Assertions.assertEquals("GET", request.method()); - Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals(443, request.url().port()); + Assertions.assertEquals("https", request.url().scheme()); + Assertions.assertEquals(443, request.url().port()); Assertions.assertTrue(request.url().pathSegments().contains("v3")); - Assertions.assertTrue(request.url().pathSegments().contains("assets")); } + Assertions.assertTrue(request.url().pathSegments().contains("assets")); + } @Test void testFetchSubfoldersByParentFolderPojo() { asset = client.stack().asset(); HashMap queryContent = new HashMap<>(); - queryContent.put("is_dir", true); + queryContent.put("is_dir", true); asset.addParam("folder", "test_folder"); - asset.addParam("include_folders", true); + asset.addParam("include_folders", true); asset.addParam("query", queryContent); queryContent.put("parent_uid", "parent_uid"); asset.addParam("query", queryContent); Request request = asset.getSubfolderAsPojo().request(); Assertions.assertEquals("GET", request.method()); - Assertions.assertEquals("https", request.url().scheme()); Assertions.assertEquals(443, request.url().port()); + Assertions.assertEquals("https", request.url().scheme()); + Assertions.assertEquals(443, request.url().port()); Assertions.assertTrue(request.url().pathSegments().contains("v3")); - Assertions.assertTrue(request.url().pathSegments().contains("assets")); } + Assertions.assertTrue(request.url().pathSegments().contains("assets")); + } + + // ==================== Asset Scanning API Tests ==================== + static String uploadedAssetUid; + + @Test + @Order(10) + void testUploadAssetIncludesScanStatusPending() throws IOException, ParseException { + Asset uploadAsset = client.stack(API_KEY, MANAGEMENT_TOKEN).asset(); + uploadAsset.addParam("include_asset_scan_status", true); + uploadAsset.addHeader("api_key", API_KEY); + uploadAsset.addHeader("authorization", MANAGEMENT_TOKEN); + Response response = uploadAsset.uploadAsset("src/test/resources/asset.png", "asset scan test upload").execute(); + Assertions.assertTrue(response.isSuccessful(), "Expected 201 on upload"); + Assertions.assertNotNull(response.body(), "Response body should not be null"); + String body = response.body().string(); + Assertions.assertTrue(body.contains("_asset_scan_status"), + "Upload response should include _asset_scan_status when param is set"); + Assertions.assertTrue(body.contains("pending"), + "Newly uploaded asset scan status should be pending"); + JSONParser parser = new JSONParser(); + JSONObject json = (JSONObject) parser.parse(body); + JSONObject assetObj = (JSONObject) json.get("asset"); + if (assetObj != null) { + uploadedAssetUid = (String) assetObj.get("uid"); + } + Assertions.assertNotNull(uploadedAssetUid, "Could not extract UID of uploaded asset"); + } + + @Test + @Order(11) + void testFindAssetsWithScanStatus() throws IOException { + Assumptions.assumeTrue(uploadedAssetUid != null, "Skipping: no uploaded asset from testUploadAssetIncludesScanStatusPending"); + asset.clearParams(); + asset.addParam("include_asset_scan_status", true); + asset.addHeader("api_key", API_KEY); + asset.addHeader("authorization", MANAGEMENT_TOKEN); + Response response = asset.find().execute(); + Assertions.assertTrue(response.isSuccessful(), "Expected 200"); + Assertions.assertNotNull(response.body()); + String body = response.body().string(); + Assertions.assertTrue(body.contains("_asset_scan_status"), + "Response should include _asset_scan_status field when param is set"); + Assertions.assertTrue(body.contains(uploadedAssetUid), + "Uploaded asset UID should appear in the list response"); + } + + @Test + @Order(12) + void testFetchAssetWithScanStatus() throws IOException { + Assumptions.assumeTrue(uploadedAssetUid != null, "Skipping: no uploaded asset from testUploadAssetIncludesScanStatusPending"); + Asset scanAsset = client.stack(API_KEY, MANAGEMENT_TOKEN).asset(uploadedAssetUid); + scanAsset.addParam("include_asset_scan_status", true); + scanAsset.addHeader("api_key", API_KEY); + scanAsset.addHeader("authorization", MANAGEMENT_TOKEN); + Response response = scanAsset.fetch().execute(); + Assertions.assertTrue(response.raw().request().url().toString().contains("include_asset_scan_status=true")); + Assertions.assertTrue(response.isSuccessful(), "Expected 200 for asset UID: " + uploadedAssetUid); + Assertions.assertNotNull(response.body()); + String body = response.body().string(); + Assertions.assertTrue(body.contains("_asset_scan_status"), + "Response should include _asset_scan_status for the uploaded asset"); + } + + @Test + @Order(13) + void testFindAssetsAllScanStatusesAreValid() throws IOException { + Asset listAsset = client.stack(API_KEY, MANAGEMENT_TOKEN).asset(); + listAsset.addParam("include_asset_scan_status", true); + listAsset.addHeader("api_key", API_KEY); + listAsset.addHeader("authorization", MANAGEMENT_TOKEN); + Response response = listAsset.find().execute(); + Assertions.assertTrue(response.isSuccessful(), "Expected 200"); + Assertions.assertNotNull(response.body()); + String body = response.body().string(); + Assertions.assertTrue(body.contains("_asset_scan_status"), + "Response should include _asset_scan_status when param is set"); + boolean hasValidStatus = body.contains("\"pending\"") || body.contains("\"clean\"") + || body.contains("\"not_scanned\"") || body.contains("\"quarantined\""); + Assertions.assertTrue(hasValidStatus, + "Every _asset_scan_status value must be one of: pending, clean, not_scanned, quarantined"); + } + + @Test + @Order(14) + void testPublishAssetWithApiVersionHeader() throws IOException { + Assumptions.assumeTrue(uploadedAssetUid != null, "Skipping: no uploaded asset from testUploadAssetIncludesScanStatusPending"); + Asset scanAsset = client.stack(API_KEY, MANAGEMENT_TOKEN).asset(uploadedAssetUid); + scanAsset.addHeader("api_key", API_KEY); + scanAsset.addHeader("authorization", MANAGEMENT_TOKEN); + scanAsset.addHeader("api_version", "3.2"); + JSONObject publishBody = new JSONObject(); + JSONObject publishDetails = new JSONObject(); + publishDetails.put("locales", new String[]{"en-us"}); + publishDetails.put("environments", new String[]{"development"}); + publishBody.put("asset", publishDetails); + Response response = scanAsset.publish(publishBody).execute(); + Assertions.assertEquals("3.2", response.raw().request().header("api_version"), + "api_version: 3.2 header must be present on publish for CDA-side scan validation"); + Assertions.assertTrue(response.isSuccessful(), "Expected 200 — publish always returns success regardless of scan status"); + Assertions.assertNotNull(response.body()); + String body = response.body().string(); + Assertions.assertTrue(body.contains("Asset sent for publishing"), + "Publish response notice should confirm asset was sent for publishing"); + } + + @Test + @Order(15) + void testFetchUploadedAssetScanStatusIsValid() throws IOException, ParseException { + Assumptions.assumeTrue(uploadedAssetUid != null, "Skipping: no uploaded asset from testUploadAssetIncludesScanStatusPending"); + Asset scanAsset = client.stack(API_KEY, MANAGEMENT_TOKEN).asset(uploadedAssetUid); + scanAsset.addParam("include_asset_scan_status", true); + scanAsset.addHeader("api_key", API_KEY); + scanAsset.addHeader("authorization", MANAGEMENT_TOKEN); + Response response = scanAsset.fetch().execute(); + Assertions.assertTrue(response.isSuccessful(), "Expected 200 for asset UID: " + uploadedAssetUid); + Assertions.assertNotNull(response.body()); + String body = response.body().string(); + Assertions.assertTrue(body.contains("_asset_scan_status"), + "Response should include _asset_scan_status when param is set"); + JSONParser parser = new JSONParser(); + JSONObject json = (JSONObject) parser.parse(body); + JSONObject assetObj = (JSONObject) json.get("asset"); + Assertions.assertNotNull(assetObj, "Response should contain an 'asset' object"); + String status = (String) assetObj.get("_asset_scan_status"); + Assertions.assertNotNull(status, "_asset_scan_status should be present in asset object"); + java.util.Set validStatuses = java.util.Set.of("pending", "clean", "not_scanned", "quarantined"); + Assertions.assertTrue(validStatuses.contains(status), + "_asset_scan_status value '" + status + "' must be one of: pending, clean, not_scanned, quarantined"); + } + + @Test + @Order(16) + void testFindAssetsWithoutScanStatusParamFieldAbsent() throws IOException { + Asset listAsset = client.stack(API_KEY, MANAGEMENT_TOKEN).asset(); + listAsset.addHeader("api_key", API_KEY); + listAsset.addHeader("authorization", MANAGEMENT_TOKEN); + Response response = listAsset.find().execute(); + Assertions.assertTrue(response.isSuccessful(), "Expected 200"); + Assertions.assertNotNull(response.body()); + String body = response.body().string(); + Assertions.assertFalse(body.contains("_asset_scan_status"), + "_asset_scan_status must be absent when include_asset_scan_status param is not passed"); + } + + @Test + @Order(17) + void testFetchAssetWithoutScanStatusParamFieldAbsent() throws IOException { + Assumptions.assumeTrue(uploadedAssetUid != null, "Skipping: no uploaded asset from testUploadAssetIncludesScanStatusPending"); + Asset scanAsset = client.stack(API_KEY, MANAGEMENT_TOKEN).asset(uploadedAssetUid); + scanAsset.addHeader("api_key", API_KEY); + scanAsset.addHeader("authorization", MANAGEMENT_TOKEN); + Response response = scanAsset.fetch().execute(); + Assertions.assertTrue(response.isSuccessful(), "Expected 200 for asset UID: " + uploadedAssetUid); + Assertions.assertNotNull(response.body()); + String body = response.body().string(); + Assertions.assertFalse(body.contains("_asset_scan_status"), + "_asset_scan_status must be absent when include_asset_scan_status param is not passed"); + } + + @Test + @Order(18) + void testPublishWithoutApiVersionHeaderIsAbsent() throws IOException { + Assumptions.assumeTrue(uploadedAssetUid != null, "Skipping: no uploaded asset from testUploadAssetIncludesScanStatusPending"); + Asset scanAsset = client.stack(API_KEY, MANAGEMENT_TOKEN).asset(uploadedAssetUid); + scanAsset.addHeader("api_key", API_KEY); + scanAsset.addHeader("authorization", MANAGEMENT_TOKEN); + JSONObject publishBody = new JSONObject(); + JSONObject publishDetails = new JSONObject(); + publishDetails.put("locales", new String[]{"en-us"}); + publishDetails.put("environments", new String[]{"development"}); + publishBody.put("asset", publishDetails); + Response response = scanAsset.publish(publishBody).execute(); + Assertions.assertNull(response.raw().request().header("api_version"), + "api_version header must be absent when not explicitly added"); + } + + @Test + @Order(19) + void testUploadWithoutScanStatusParamFieldAbsent() throws IOException { + Asset uploadAsset = client.stack(API_KEY, MANAGEMENT_TOKEN).asset(); + uploadAsset.addHeader("api_key", API_KEY); + uploadAsset.addHeader("authorization", MANAGEMENT_TOKEN); + Response response = uploadAsset.uploadAsset("src/test/resources/asset.png", "negative scan test upload").execute(); + Assertions.assertTrue(response.isSuccessful(), "Expected 201 on upload"); + Assertions.assertNotNull(response.body()); + String body = response.body().string(); + Assertions.assertFalse(body.contains("_asset_scan_status"), + "_asset_scan_status must be absent in upload response when param is not passed"); + try { + JSONParser parser = new JSONParser(); + JSONObject json = (JSONObject) parser.parse(body); + JSONObject assetObj = (JSONObject) json.get("asset"); + if (assetObj != null) { + String negativeTestUid = (String) assetObj.get("uid"); + if (negativeTestUid != null) { + Asset toDelete = client.stack(API_KEY, MANAGEMENT_TOKEN).asset(negativeTestUid); + toDelete.addHeader("api_key", API_KEY); + toDelete.addHeader("authorization", MANAGEMENT_TOKEN); + toDelete.delete().execute(); + } + } + } catch (ParseException ignored) {} + } + + @Test + @Order(20) + void testDeleteUploadedScanAsset() throws IOException { + Assumptions.assumeTrue(uploadedAssetUid != null, "Skipping: no uploaded asset to clean up"); + Asset uploadedAsset = client.stack(API_KEY, MANAGEMENT_TOKEN).asset(uploadedAssetUid); + uploadedAsset.addHeader("api_key", API_KEY); + uploadedAsset.addHeader("authorization", MANAGEMENT_TOKEN); + Response response = uploadedAsset.delete().execute(); + Assertions.assertTrue(response.isSuccessful(), "Expected 200 on delete for asset UID: " + uploadedAssetUid); + } + + // ==================== End Asset Scanning API Tests ==================== @Test @Disabled("disabled to avoid unnecessary asset creation, Tested working fine") @@ -327,8 +537,8 @@ void uploadFile() throws Exception { Asset asset = stack.asset(); String fileName = "/Users/reeshika.hosmani/Downloads/surf-svgrepo-com.svg", parentFolder = "bltd1150f1f7d9411e5", title = "Vacation icon"; String[] tags = {"icon"}; - Response response = asset.uploadAsset(fileName,parentFolder,title,"",tags).execute(); - if(response.isSuccessful()){ + Response response = asset.uploadAsset(fileName, parentFolder, title, "", tags).execute(); + if (response.isSuccessful()) { System.out.println("uploaded asset successfully:" + response.body().string()); } else { System.out.println("Error in uploading" + response.errorBody().string()); diff --git a/src/test/java/com/contentstack/cms/stack/AssetUnitTest.java b/src/test/java/com/contentstack/cms/stack/AssetUnitTest.java index f9c2ad71..0798745a 100644 --- a/src/test/java/com/contentstack/cms/stack/AssetUnitTest.java +++ b/src/test/java/com/contentstack/cms/stack/AssetUnitTest.java @@ -10,9 +10,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.Objects; @Tag("unit") -class AssetUnitTest { +public class AssetUnitTest { protected static String AUTHTOKEN = TestClient.AUTHTOKEN; protected static String API_KEY = TestClient.API_KEY; @@ -66,7 +67,7 @@ void testAssetSingle() { Request resp = asset.fetch().request(); Assertions.assertTrue(resp.isHttps()); Assertions.assertEquals("GET", resp.method()); - Assertions.assertEquals(2, resp.headers().size()); + Assertions.assertEquals(3, resp.headers().size()); Collection matcher = new ArrayList<>(); matcher.add("api_key"); matcher.add("authorization"); @@ -146,7 +147,7 @@ void testAssetReplace() { .request(); Assertions.assertTrue(resp.isHttps()); Assertions.assertEquals("PUT", resp.method()); - Assertions.assertEquals(2, resp.headers().size()); + Assertions.assertEquals(3, resp.headers().size()); Collection matcher = new ArrayList<>(); matcher.add("api_key"); matcher.add("authorization"); @@ -196,7 +197,7 @@ void testAssetDelete() { Request resp = asset.delete().request(); Assertions.assertTrue(resp.isHttps()); Assertions.assertEquals("DELETE", resp.method()); - Assertions.assertEquals(2, resp.headers().size()); + Assertions.assertEquals(3, resp.headers().size()); Collection matcher = new ArrayList<>(); matcher.add("api_key"); matcher.add("authorization"); @@ -210,7 +211,7 @@ void testAssetRteInformation() { Request resp = asset.rteInformation().request(); Assertions.assertTrue(resp.isHttps()); Assertions.assertEquals("GET", resp.method()); - Assertions.assertEquals(2, resp.headers().size()); + Assertions.assertEquals(3, resp.headers().size()); Collection matcher = new ArrayList<>(); matcher.add("api_key"); matcher.add("authorization"); @@ -228,7 +229,7 @@ void testAssetSetVersionName() { Request resp = asset.setVersionName(2, body).request(); Assertions.assertTrue(resp.isHttps()); Assertions.assertEquals("POST", resp.method()); - Assertions.assertEquals(2, resp.headers().size()); + Assertions.assertEquals(3, resp.headers().size()); Collection matcher = new ArrayList<>(); matcher.add("api_key"); matcher.add("authorization"); @@ -261,7 +262,7 @@ void testAssetDeleteVersionName() { Request resp = asset.deleteVersionName(2).request(); Assertions.assertTrue(resp.isHttps()); Assertions.assertEquals("DELETE", resp.method()); - Assertions.assertEquals(2, resp.headers().size()); + Assertions.assertEquals(3, resp.headers().size()); Collection matcher = new ArrayList<>(); matcher.add("api_key"); matcher.add("authorization"); @@ -365,7 +366,7 @@ void testAssetUnpublish() { Request resp = asset.unpublish(_body).request(); Assertions.assertTrue(resp.isHttps()); Assertions.assertEquals("POST", resp.method()); - Assertions.assertEquals(2, resp.headers().size()); + Assertions.assertEquals(3, resp.headers().size()); Collection matcher = new ArrayList<>(); matcher.add("api_key"); matcher.add("authorization"); @@ -382,7 +383,7 @@ void testAssetSingleFolder() { Request resp = assetFolder.fetch().request(); Assertions.assertTrue(resp.isHttps()); Assertions.assertEquals("GET", resp.method()); - Assertions.assertEquals(0, resp.headers().size()); + Assertions.assertEquals(3, resp.headers().size()); Collection matcher = new ArrayList<>(); matcher.add("api_key"); matcher.add("authorization"); @@ -443,7 +444,7 @@ void testAssetCreateFolder() { Request resp = asset.folder().create(_body).request(); Assertions.assertTrue(resp.isHttps()); Assertions.assertEquals("POST", resp.method()); - Assertions.assertEquals(0, resp.headers().size()); + Assertions.assertEquals(3, resp.headers().size()); Collection matcher = new ArrayList<>(); matcher.add("api_key"); matcher.add("authorization"); @@ -465,7 +466,7 @@ void testAssetUpdateFolder() { Request resp = asset.folder(_uid).update(_body).request(); Assertions.assertTrue(resp.isHttps()); Assertions.assertEquals("PUT", resp.method()); - Assertions.assertEquals(0, resp.headers().size()); + Assertions.assertEquals(2, resp.headers().size()); Collection matcher = new ArrayList<>(); matcher.add("api_key"); matcher.add("authorization"); @@ -480,7 +481,7 @@ void testAssetDeleteFolder() { Request resp = asset.folder(_uid).delete().request(); Assertions.assertTrue(resp.isHttps()); Assertions.assertEquals("DELETE", resp.method()); - Assertions.assertEquals(0, resp.headers().size()); + Assertions.assertEquals(2, resp.headers().size()); Collection matcher = new ArrayList<>(); matcher.add("api_key"); matcher.add("authorization"); @@ -489,6 +490,106 @@ void testAssetDeleteFolder() { resp.url().query()); Assertions.assertNull(resp.body()); } + // ==================== Asset Scanning Tests ==================== + // Feature: Asset Scanning (plan-based; enabled by default for orgs with damAccess) + // Possible _asset_scan_status values: pending | clean | quarantined | not_scanned + // not_scanned — returned for assets uploaded before scanning was enabled on the stack. + // API tests against a scan-enabled stack are in AssetAPITest. + // ============================================================== + + @Test + void testFetchIncludesScanStatusParam() { + asset.clearParams(); + asset.addParam("include_asset_scan_status", true); + Request resp = asset.fetch().request(); + Assertions.assertEquals("GET", resp.method()); + Assertions.assertEquals("/v3/assets/" + _uid, resp.url().encodedPath()); + Assertions.assertTrue( + Objects.requireNonNull(resp.url().query()).contains("include_asset_scan_status=true")); + Assertions.assertNull(resp.body()); + } + + @Test + void testFindIncludesScanStatusParam() { + asset.clearParams(); + asset.addParam("include_asset_scan_status", true); + Request resp = asset.find().request(); + Assertions.assertEquals("GET", resp.method()); + Assertions.assertEquals("/v3/assets", resp.url().encodedPath()); + Assertions.assertTrue( + Objects.requireNonNull(resp.url().query()).contains("include_asset_scan_status=true")); + Assertions.assertNull(resp.body()); + } + + @Test + void testUploadIncludesScanStatusParam() { + asset.clearParams(); + asset.addParam("include_asset_scan_status", true); + String filePath = "src/test/resources/asset.png"; + Request resp = asset.uploadAsset(filePath, "test upload for scan status").request(); + Assertions.assertEquals("POST", resp.method()); + Assertions.assertEquals("/v3/assets", resp.url().encodedPath()); + Assertions.assertTrue( + Objects.requireNonNull(resp.url().query()).contains("include_asset_scan_status=true")); + Assertions.assertNotNull(resp.body()); + } + + @Test + void testFetchWithoutScanStatusParamExcludesField() { + // Omitting the param means _asset_scan_status is not requested. + // Legacy assets (not_scanned) and new assets alike will not include the field. + asset.clearParams(); + Request resp = asset.fetch().request(); + String query = resp.url().query(); + Assertions.assertTrue(query == null || !query.contains("include_asset_scan_status"), + "include_asset_scan_status must be absent when not explicitly set"); + Assertions.assertNull(resp.body()); + } + + @Test + void testScanStatusParamCoexistsWithOtherParams() { + asset.clearParams(); + asset.addParam("include_asset_scan_status", true); + asset.addParam("include_publish_details", true); + asset.addParam("environment", "production"); + Request resp = asset.fetch().request(); + String query = Objects.requireNonNull(resp.url().query()); + Assertions.assertTrue(query.contains("include_asset_scan_status=true")); + Assertions.assertTrue(query.contains("include_publish_details=true")); + Assertions.assertTrue(query.contains("environment=production")); + } + + @Test + void testPublishIncludesApiVersionHeader() { + // api_version: 3.2 is required for CDA-side scan validation on publish. + // Without it, quarantined assets incorrectly appear as published in the UI. + asset.clearParams(); + asset.addHeader("api_version", "3.2"); + JSONObject body = new JSONObject(); + Request resp = asset.publish(body).request(); + Assertions.assertEquals("POST", resp.method()); + Assertions.assertEquals("/v3/assets/" + _uid + "/publish", resp.url().encodedPath()); + Assertions.assertEquals("3.2", resp.header("api_version")); + Assertions.assertNotNull(resp.body()); + } + + @Test + void testApiVersionHeaderScopedToPublish() { + // api_version: 3.2 must be set explicitly per-call and must not be a global header. + // Setting it globally would affect all API calls and could break other functionality. + HashMap headers = new HashMap<>(); + headers.put(Util.API_KEY, API_KEY); + headers.put(Util.AUTHORIZATION, MANAGEMENT_TOKEN); + Asset freshAsset = TestClient.getClient().stack(headers).asset(_uid); + freshAsset.clearParams(); + Request resp = freshAsset.fetch().request(); + Assertions.assertEquals("GET", resp.method()); + Assertions.assertNull(resp.header("api_version"), + "api_version header must not be present on fetch unless explicitly added"); + } + + // ==================== End Asset Scanning Tests ==================== + @Test void testAssetValidate() { asset = TestClient.getStack().asset(); From 67a83bdb42297017d4296c88b00f27c8576b4ad2 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 24 Jun 2026 14:45:27 +0530 Subject: [PATCH 193/197] fix: update version to 1.12.1 and add hostname validation to prevent SSRF --- changelog.md | 6 ++++ pom.xml | 2 +- .../com/contentstack/cms/Contentstack.java | 32 +++++++++++++++++-- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 5f5c3c64..b364d46a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## v1.12.1 + +### Jun 29, 2026 + +- Snyk fix + ## v1.12.0 ### Jun 15, 2026 diff --git a/pom.xml b/pom.xml index 1f203bed..f81fe82f 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.12.0 + 1.12.1 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 69295d04..933bb717 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -747,10 +747,35 @@ public Builder setRetry(@NotNull Boolean retry) { * @return Client host */ public Builder setHost(@NotNull String hostname) { - this.hostname = hostname; + this.hostname = validateHostname(hostname); return this; } + /** + * Validates that the supplied hostname is a bare host (optionally with a + * port) and does not smuggle a scheme, credentials, path, query, or other + * characters that could redirect outbound requests to an unintended + * destination. This guards against Server-Side Request Forgery (SSRF) + * when the host is sourced from untrusted input. + * + * @param hostname the candidate host + * @return the validated host, unchanged + * @throws IllegalArgumentException if the host is null, blank, or malformed + */ + private static String validateHostname(String hostname) { + if (hostname == null || hostname.trim().isEmpty()) { + throw new IllegalArgumentException("Hostname must not be null or empty"); + } + String host = hostname.trim(); + // Reject embedded scheme, credentials, path/query/fragment, whitespace, + // and other characters that would change the request target. + if (!host.matches("^[A-Za-z0-9.-]+(:\\d{1,5})?$")) { + throw new IllegalArgumentException( + "Invalid hostname: '" + hostname + "'. Expected a bare host name with an optional port."); + } + return host; + } + /** * Configures the client to target a specific Contentstack region by resolving * the correct Content Management API host from the bundled regions registry. @@ -974,7 +999,10 @@ public Contentstack build() { } private void validateClient(Contentstack contentstack) { - String baseUrl = Util.PROTOCOL + "://" + this.hostname + "/" + version + "/"; + // Re-validate the host at build time so SSRF protection applies regardless + // of how the hostname was set (setHost, region resolution, or default). + String validatedHost = validateHostname(this.hostname); + String baseUrl = Util.PROTOCOL + "://" + validatedHost + "/" + version + "/"; this.instance = new Retrofit.Builder().baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create()) .client(httpClient(contentstack, this.retry)).build(); From f0d252a7010ac7e4153ddd13b45f5a763e5f6774 Mon Sep 17 00:00:00 2001 From: Aravind Kumar Date: Tue, 30 Jun 2026 20:28:38 +0530 Subject: [PATCH 194/197] Delete issues-jira.yml --- .github/workflows/issues-jira.yml | 118 ------------------------------ 1 file changed, 118 deletions(-) delete mode 100644 .github/workflows/issues-jira.yml diff --git a/.github/workflows/issues-jira.yml b/.github/workflows/issues-jira.yml deleted file mode 100644 index 5f65d0b6..00000000 --- a/.github/workflows/issues-jira.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: Create Jira Ticket for Github Issue - -on: - issues: - types: [opened, reopened] - -jobs: - issue-jira: - runs-on: ubuntu-latest - steps: - - name: Create Jira Issue - id: create_jira - uses: actions/github-script@v9 - with: - script: | - const baseUrl = process.env.JIRA_BASE_URL; - const userEmail = process.env.JIRA_USER_EMAIL; - const jiraToken = process.env.JIRA_API_TOKEN; - const jiraProject = process.env.JIRA_PROJECT; - const jiraIssueType = process.env.JIRA_ISSUE_TYPE; - const jiraFields = JSON.parse(process.env.ISSUES_JIRA_FIELDS); - - let requestBody = JSON.stringify({ - fields: { - ...jiraFields, - "project": { - "key": jiraProject - }, - "issuetype": { - "name": jiraIssueType - }, - "summary": "Github | Issue | ${{ github.event.repository.name }} | ${{ github.event.issue.title }}", - "description": { - "version": 1, - "type": "doc", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Github Issue", - "marks": [ - { - "type": "strong" - } - ] - }, - { - "type": "text", - "text": ": " - }, - { - "type": "text", - "text": "${{ github.event.issue.html_url }}", - "marks": [ - { - "type": "link", - "attrs": { - "href": "${{ github.event.issue.html_url }}" - } - } - ] - } - ] - }, - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "Description", - "marks": [ - { - "type": "strong" - } - ] - }, - { - "type": "text", - "text": ":" - } - ] - }, - { - "type": "codeBlock", - "content": [ - { - "type": "text", - "text": `${{ github.event.issue.body }}` - } - ] - } - ] - } - } - }); - - const response = await fetch(`${baseUrl}/rest/api/3/issue`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Basic ${btoa(userEmail + ":" + jiraToken)}` - }, - body: requestBody - }); - if (!response.ok) { - throw new Error(`JIRA API error! Status: ${response.status}`); - } - const data = await response.json(); - console.log('Jira Issue Created:', data.key); - env: - JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} - JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} - JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }} - JIRA_ISSUE_TYPE: ${{ secrets.JIRA_ISSUE_TYPE }} - ISSUES_JIRA_FIELDS: "${{ secrets.ISSUES_JIRA_FIELDS }}" From 5cc200fe583f6daf213f352f14b94745b0ba9b48 Mon Sep 17 00:00:00 2001 From: harshitha-cstk Date: Wed, 1 Jul 2026 15:55:27 +0530 Subject: [PATCH 195/197] snyk fixes --- .../com/contentstack/cms/Contentstack.java | 14 ++- .../com/contentstack/cms/core/Endpoint.java | 50 +++++++++- .../contentstack/cms/models/OAuthConfig.java | 92 ++++++++++++++----- .../com/contentstack/cms/oauth/OAuthTest.java | 50 ++++++++++ 4 files changed, 179 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index 933bb717..e0f1f7f5 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -965,8 +965,7 @@ public Builder setOAuth(String appId, String clientId, String redirectUri, Strin OAuthConfig.OAuthConfigBuilder builder = OAuthConfig.builder() .appId(appId) .clientId(clientId) - .redirectUri(redirectUri) - .host(host); + .redirectUri(redirectUri); // Only set clientSecret if provided (otherwise PKCE flow will be used) if (clientSecret != null && !clientSecret.trim().isEmpty()) { @@ -978,7 +977,16 @@ public Builder setOAuth(String appId, String clientId, String redirectUri, Strin builder.tokenCallback(this.tokenCallback); } - this.oauthConfig = builder.build(); + // Validate the required fields first so their errors surface ahead of + // host validation, matching the previous configuration-error behaviour. + builder.build().validate(); + + // Validate the host before it is stored on the OAuth config, so the same + // SSRF protection that guards the main API host also applies here + // regardless of how the host was sourced (setHost, region resolution, + // or a value passed directly by the caller). + String validatedHost = host != null ? validateHostname(host) : null; + this.oauthConfig = builder.host(validatedHost).build(); return this; } diff --git a/src/main/java/com/contentstack/cms/core/Endpoint.java b/src/main/java/com/contentstack/cms/core/Endpoint.java index 1d5afabb..74d67242 100644 --- a/src/main/java/com/contentstack/cms/core/Endpoint.java +++ b/src/main/java/com/contentstack/cms/core/Endpoint.java @@ -42,6 +42,18 @@ public class Endpoint { private static final String REGIONS_URL = "https://artifacts.contentstack.com/regions.json"; private static final String REGIONS_RESOURCE = "regions.json"; + /** + * Domain suffixes that endpoint URLs are permitted to target. The regions + * registry is fetched from a remote resource, so every URL derived from it + * is validated against this allowlist before it can be used as a request + * target. This prevents Server-Side Request Forgery (SSRF) should the + * registry ever be tampered with or point at an unexpected host. + */ + private static final String[] TRUSTED_DOMAIN_SUFFIXES = { + ".contentstack.com", + ".contentstack.io" + }; + private static volatile JsonArray regionsData = null; private Endpoint() {} @@ -82,7 +94,7 @@ public static String getContentstackEndpoint(String region, String service, bool throw new IllegalArgumentException( "Service \"" + service + "\" not found for region \"" + regionRow.get("id").getAsString() + "\""); } - String url = endpoints.get(service).getAsString(); + String url = validateTrustedEndpoint(endpoints.get(service).getAsString()); return omitHttps ? stripHttps(url) : url; } @@ -113,7 +125,7 @@ public static Map getContentstackEndpoints(String region, boolea Map result = new LinkedHashMap<>(); if (endpoints != null) { for (Map.Entry entry : endpoints.entrySet()) { - String url = entry.getValue().getAsString(); + String url = validateTrustedEndpoint(entry.getValue().getAsString()); result.put(entry.getKey(), omitHttps ? stripHttps(url) : url); } } @@ -227,6 +239,40 @@ public static synchronized int refresh() { } } + /** + * Validates that an endpoint URL sourced from the regions registry is a + * well-formed HTTPS URL whose host belongs to a trusted Contentstack domain. + * + *

    The regions registry is loaded from a remote resource; constraining the + * host to an allowlist here neutralizes any Server-Side Request Forgery (SSRF) + * risk before the value can flow into an outbound request. + * + * @param url the endpoint URL read from the registry + * @return the URL, unchanged, when it targets a trusted host + * @throws IllegalArgumentException if the URL is malformed, not HTTPS, or + * targets an untrusted host + */ + private static String validateTrustedEndpoint(String url) { + URL parsed; + try { + parsed = new URL(url); + } catch (java.net.MalformedURLException e) { + throw new IllegalArgumentException("Malformed endpoint URL in regions registry: " + url, e); + } + if (!"https".equalsIgnoreCase(parsed.getProtocol())) { + throw new IllegalArgumentException("Endpoint URL must use HTTPS: " + url); + } + String host = parsed.getHost() == null ? "" : parsed.getHost().toLowerCase(); + for (String suffix : TRUSTED_DOMAIN_SUFFIXES) { + // suffix begins with '.', so "contentstack.com" itself is matched via + // the leading-dot form only for subdomains; also allow the bare apex. + if (host.endsWith(suffix) || host.equals(suffix.substring(1))) { + return url; + } + } + throw new IllegalArgumentException("Endpoint URL targets an untrusted host: " + url); + } + private static String stripHttps(String url) { return url.replaceAll("^https?://", ""); } diff --git a/src/main/java/com/contentstack/cms/models/OAuthConfig.java b/src/main/java/com/contentstack/cms/models/OAuthConfig.java index 0b29f02a..f998b345 100644 --- a/src/main/java/com/contentstack/cms/models/OAuthConfig.java +++ b/src/main/java/com/contentstack/cms/models/OAuthConfig.java @@ -73,20 +73,18 @@ public boolean isPkceEnabled() { */ public String getFormattedAuthorizationEndpoint() { if (authEndpoint != null) { - return authEndpoint; + return validateHttpsEndpoint(authEndpoint); } - String hostname = host != null ? host : Util.OAUTH_APP_HOST; + // Only use the configured host when it is a genuine Contentstack host; + // otherwise fall back to the default. This prevents SSRF via a host such + // as "evil-contentstack.attacker.com" that merely contains the substring. + String hostname = isTrustedContentstackHost(host) ? host : Util.OAUTH_APP_HOST; - // Transform hostname if needed - if (hostname.contains("contentstack")) { - hostname = hostname - .replaceAll("-api\\.", "-app.") // eu-api.contentstack.com -> eu-app.contentstack.com - .replaceAll("^api\\.", "app.") // api.contentstack.io -> app.contentstack.io - .replaceAll("\\.io$", ".com"); // *.io -> *.com - } else { - hostname = Util.OAUTH_APP_HOST; - } + hostname = hostname + .replaceAll("-api\\.", "-app.") // eu-api.contentstack.com -> eu-app.contentstack.com + .replaceAll("^api\\.", "app.") // api.contentstack.io -> app.contentstack.io + .replaceAll("\\.io$", ".com"); // *.io -> *.com return "https://" + hostname + String.format(Util.OAUTH_AUTHORIZE_ENDPOINT, appId); } @@ -98,24 +96,74 @@ public String getFormattedAuthorizationEndpoint() { */ public String getTokenEndpoint() { if (tokenEndpoint != null) { - return tokenEndpoint; + return validateHttpsEndpoint(tokenEndpoint); } - String hostname = host != null ? host : Util.OAUTH_API_HOST; + // Only use the configured host when it is a genuine Contentstack host; + // otherwise fall back to the default. This prevents SSRF via a host such + // as "evil-contentstack.attacker.com" that merely contains the substring. + String hostname = isTrustedContentstackHost(host) ? host : Util.OAUTH_API_HOST; - // Transform hostname if needed - if (hostname.contains("contentstack")) { - hostname = hostname - .replaceAll("-api\\.", "-developerhub-api.") // eu-api.contentstack.com -> eu-developerhub-api.contentstack.com - .replaceAll("^api\\.", "developerhub-api.") // api.contentstack.io -> developerhub-api.contentstack.io - .replaceAll("\\.io$", ".com"); // *.io -> *.com - } else { - hostname = Util.OAUTH_API_HOST; - } + hostname = hostname + .replaceAll("-api\\.", "-developerhub-api.") // eu-api.contentstack.com -> eu-developerhub-api.contentstack.com + .replaceAll("^api\\.", "developerhub-api.") // api.contentstack.io -> developerhub-api.contentstack.io + .replaceAll("\\.io$", ".com"); // *.io -> *.com return "https://" + hostname + Util.OAUTH_TOKEN_ENDPOINT; } + /** + * Determines whether the supplied host is a bare Contentstack hostname that + * is safe to use as an outbound request target. + * + *

    The host must be a bare host name (optionally with a port) — no embedded + * scheme, credentials, path, query, fragment, or whitespace — and must resolve + * to a {@code contentstack.com} or {@code contentstack.io} domain. This guards + * against Server-Side Request Forgery (SSRF) when the host originates from + * untrusted input. + * + * @param candidate the candidate host + * @return true if the host is a trusted, well-formed Contentstack host + */ + private static boolean isTrustedContentstackHost(String candidate) { + if (candidate == null) { + return false; + } + String host = candidate.trim(); + // Reject embedded scheme, credentials, path/query/fragment, whitespace, + // and any other characters that would change the request target. + if (!host.matches("^[A-Za-z0-9.-]+(:\\d{1,5})?$")) { + return false; + } + int portIndex = host.indexOf(':'); + String hostOnly = (portIndex >= 0 ? host.substring(0, portIndex) : host).toLowerCase(); + return hostOnly.equals("contentstack.com") + || hostOnly.equals("contentstack.io") + || hostOnly.endsWith(".contentstack.com") + || hostOnly.endsWith(".contentstack.io"); + } + + /** + * Validates that an explicitly configured OAuth endpoint is a well-formed + * absolute HTTPS URL. Rejecting non-HTTPS schemes prevents the endpoint from + * being pointed at internal services or non-web protocols (SSRF). + * + * @param endpoint the configured endpoint URL + * @return the endpoint, unchanged, when valid + * @throws IllegalArgumentException if the endpoint is not a valid HTTPS URL + */ + private static String validateHttpsEndpoint(String endpoint) { + try { + URL url = new URL(endpoint); + if (!"https".equalsIgnoreCase(url.getProtocol())) { + throw new IllegalArgumentException("OAuth endpoint must use HTTPS: " + endpoint); + } + return endpoint; + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Invalid OAuth endpoint URL: " + endpoint, e); + } + } + /** * Gets the response type, defaulting to "code" * diff --git a/src/test/java/com/contentstack/cms/oauth/OAuthTest.java b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java index ea664960..c0cc32c0 100644 --- a/src/test/java/com/contentstack/cms/oauth/OAuthTest.java +++ b/src/test/java/com/contentstack/cms/oauth/OAuthTest.java @@ -335,6 +335,56 @@ public void testCustomEndpoints() { customTokenEndpoint, tokenUrl); } + @Test + public void testSsrfHostIsRejected() { + // Hosts that merely contain the "contentstack" substring, or that smuggle + // a scheme/credentials/path, must NOT be used as the request target. + String[] maliciousHosts = { + "evil-contentstack.attacker.com", + "contentstack.attacker.com", + "attacker.com", + "attacker.com/contentstack.com", + "user@contentstack.com.attacker.com", + "http://contentstack.com@attacker.com" + }; + + for (String malicious : maliciousHosts) { + OAuthConfig config = OAuthConfig.builder() + .appId(TEST_APP_ID) + .clientId(TEST_CLIENT_ID) + .redirectUri(TEST_REDIRECT_URI) + .host(malicious) + .build(); + + String authUrl = config.getFormattedAuthorizationEndpoint(); + String tokenUrl = config.getTokenEndpoint(); + + assertFalse("Auth URL must not target malicious host " + malicious + ": " + authUrl, + authUrl.contains("attacker.com")); + assertFalse("Token URL must not target malicious host " + malicious + ": " + tokenUrl, + tokenUrl.contains("attacker.com")); + // The request target must fall back to a genuine Contentstack domain. + assertTrue("Auth URL should target a Contentstack host: " + authUrl, + authUrl.startsWith("https://") && authUrl.contains(".contentstack.com/")); + assertTrue("Token URL should target a Contentstack host: " + tokenUrl, + tokenUrl.startsWith("https://") && tokenUrl.contains(".contentstack.com/")); + } + } + + @Test + public void testNonHttpsCustomEndpointRejected() { + // An explicitly configured endpoint must be HTTPS; non-HTTPS schemes that + // could reach internal services or non-web protocols are rejected. + OAuthConfig config = OAuthConfig.builder() + .appId(TEST_APP_ID) + .clientId(TEST_CLIENT_ID) + .redirectUri(TEST_REDIRECT_URI) + .tokenEndpoint("http://169.254.169.254/latest/meta-data") + .build(); + + assertThrows(IllegalArgumentException.class, config::getTokenEndpoint); + } + // ================= From 06bb98ed8529e34148ac9176056548271201f50c Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 1 Jul 2026 16:47:39 +0530 Subject: [PATCH 196/197] fix: update version to 1.12.2 and add changelog entry for Snyk fix --- changelog.md | 6 ++++++ pom.xml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index b364d46a..da5b3d9e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## v1.12.2 + +### Jul 06, 2026 + +- Snyk fix + ## v1.12.1 ### Jun 29, 2026 diff --git a/pom.xml b/pom.xml index f81fe82f..57672e64 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ cms jar contentstack-management-java - 1.12.1 + 1.12.2 Contentstack Java Management SDK for Content Management API, Contentstack is a headless CMS with an API-first approach From e31021d0b8060f53fac9c506b96ad6f5f4100f84 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 28 Jul 2026 11:33:56 +0530 Subject: [PATCH 197/197] feat: add taxonomy and term publish, unpublish, localize, and unlocalize support Closes the parity gap with the JS SDK per DX-9676 by adding CMA taxonomy publish/unpublish and taxonomy/term-level localize/unlocalize methods, each live-verified against a real stack. Co-Authored-By: Claude Sonnet 5 --- .../contentstack/cms/core/ErrorMessages.java | 1 + .../com/contentstack/cms/stack/Taxonomy.java | 59 +++++++ .../cms/stack/TaxonomyService.java | 38 +++++ .../com/contentstack/cms/stack/Terms.java | 31 ++++ .../com/contentstack/cms/UnitTestSuite.java | 4 +- .../cms/stack/TaxonomyAPITest.java | 90 +++++++++++ .../contentstack/cms/stack/TaxonomyTest.java | 148 +++++++++++++++++- src/test/resources/mocktaxonomy/localize.json | 7 + .../resources/mocktaxonomy/localizeTerm.json | 8 + src/test/resources/mocktaxonomy/publish.json | 6 + 10 files changed, 390 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/mocktaxonomy/localize.json create mode 100644 src/test/resources/mocktaxonomy/localizeTerm.json create mode 100644 src/test/resources/mocktaxonomy/publish.json diff --git a/src/main/java/com/contentstack/cms/core/ErrorMessages.java b/src/main/java/com/contentstack/cms/core/ErrorMessages.java index 477809fa..420a0743 100644 --- a/src/main/java/com/contentstack/cms/core/ErrorMessages.java +++ b/src/main/java/com/contentstack/cms/core/ErrorMessages.java @@ -36,6 +36,7 @@ private ErrorMessages() { public static final String PUBLISH_QUEUE_UID_REQUIRED = "Publish Queue UID is required. Provide a valid Publish Queue UID and try again."; public static final String RELEASE_UID_REQUIRED = "Release UID is required. Provide a valid Release UID and try again."; public static final String ROLE_UID_REQUIRED = "Role UID is required. Provide a valid Role UID and try again."; + public static final String TAXONOMY_UID_REQUIRED = "Taxonomy UID is required. Provide a valid Taxonomy UID and try again."; public static final String VARIANT_GROUP_UID_REQUIRED = "Variant Group UID is required. Provide a valid Variant Group UID and try again."; public static final String WEBHOOK_UID_REQUIRED = "Webhook UID is required. Provide a valid Webhook UID and try again."; public static final String WORKFLOW_UID_REQUIRED = "Workflow UID is required. Provide a valid Workflow UID and try again."; diff --git a/src/main/java/com/contentstack/cms/stack/Taxonomy.java b/src/main/java/com/contentstack/cms/stack/Taxonomy.java index b3abca53..ea81cb6d 100644 --- a/src/main/java/com/contentstack/cms/stack/Taxonomy.java +++ b/src/main/java/com/contentstack/cms/stack/Taxonomy.java @@ -1,6 +1,7 @@ package com.contentstack.cms.stack; import com.contentstack.cms.BaseImplementation; +import com.contentstack.cms.core.ErrorMessages; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; @@ -8,6 +9,7 @@ import retrofit2.Retrofit; import java.util.HashMap; +import java.util.Objects; /** @@ -229,6 +231,63 @@ public Call delete(@NotNull String taxonomyId) { return this.taxonomyService.delete(this.headers, taxonomyId); } + /** + * Publish one or more taxonomies to the given environments/locales. + * Call on a collection-level instance, e.g. {@code stack.taxonomy()}. + * + * @param body the request body, e.g. {@code {"locales": [...], "environments": [...], "items": [{"uid": "..."}]}} + * @return the call Example

         {@code
    +     *     Response response = taxonomy.publish(body).execute();
    +     *     } 
    + */ + public Call publish(@NotNull JSONObject body) { + return this.taxonomyService.publish(this.headers, body); + } + + /** + * Unpublish one or more taxonomies from the given environments/locales. + * Call on a collection-level instance, e.g. {@code stack.taxonomy()}. + * + * @param body the request body, e.g. {@code {"locales": [...], "environments": [...], "items": [{"uid": "..."}]}} + * @return the call Example
         {@code
    +     *     Response response = taxonomy.unpublish(body).execute();
    +     *     } 
    + */ + public Call unpublish(@NotNull JSONObject body) { + return this.taxonomyService.unpublish(this.headers, body); + } + + /** + * Localize a taxonomy into the given locale. + * Call on an instance-level taxonomy, e.g. {@code stack.taxonomy("taxonomyId")}. + * + * @param body the request body, e.g. {@code {"taxonomy": {"name": "..."}}} + * @param locale the target locale, e.g. {@code hi-in} + * @return the call Example
         {@code
    +     *     Response response = taxonomy.localize(body, "hi-in").execute();
    +     *     } 
    + */ + public Call localize(@NotNull JSONObject body, @NotNull String locale) { + Objects.requireNonNull(this.taxonomyId, ErrorMessages.TAXONOMY_UID_REQUIRED); + this.params.put("locale", locale); + return this.taxonomyService.localize(this.headers, this.taxonomyId, this.params, body); + } + + /** + * Unlocalize a taxonomy from the given locale. + * Call on an instance-level taxonomy, e.g. {@code stack.taxonomy("taxonomyId")}. + * + * @param locale the locale to remove, e.g. {@code hi-in} + * @return the call Example
         {@code
    +     *     Response response = taxonomy.unlocalize("hi-in").execute();
    +     *     } 
    + */ + public Call unlocalize(@NotNull String locale) { + Objects.requireNonNull(this.taxonomyId, ErrorMessages.TAXONOMY_UID_REQUIRED); + this.params.put("locale", locale); + return this.taxonomyService.unlocalize(this.headers, this.taxonomyId, this.params); + } + /** * Clear params for internal uses only for testing diff --git a/src/main/java/com/contentstack/cms/stack/TaxonomyService.java b/src/main/java/com/contentstack/cms/stack/TaxonomyService.java index 571a9bcb..ae96ed9e 100644 --- a/src/main/java/com/contentstack/cms/stack/TaxonomyService.java +++ b/src/main/java/com/contentstack/cms/stack/TaxonomyService.java @@ -37,6 +37,29 @@ Call delete( @HeaderMap Map headers, @Path("taxonomy_uid") String uid); + @POST("taxonomies/publish") + Call publish( + @HeaderMap Map headers, + @Body JSONObject body); + + @POST("taxonomies/unpublish") + Call unpublish( + @HeaderMap Map headers, + @Body JSONObject body); + + @POST("taxonomies/{taxonomy_uid}") + Call localize( + @HeaderMap Map headers, + @Path("taxonomy_uid") String uid, + @QueryMap Map params, + @Body JSONObject body); + + @DELETE("taxonomies/{taxonomy_uid}") + Call unlocalize( + @HeaderMap Map headers, + @Path("taxonomy_uid") String uid, + @QueryMap Map params); + // --Terms-- @POST("taxonomies/{taxonomy_uid}/terms") @@ -87,6 +110,21 @@ Call reorder( @QueryMap Map queryParams, @Body JSONObject body); + @POST("taxonomies/{taxonomy_uid}/terms/{term_id}") + Call localizeTerm( + @HeaderMap HashMap headers, + @Path("taxonomy_uid") String taxonomyId, + @Path("term_id") String termId, + @QueryMap Map queryParams, + @Body JSONObject body); + + @DELETE("taxonomies/{taxonomy_uid}/terms/{term_id}") + Call unlocalizeTerm( + @HeaderMap HashMap headers, + @Path("taxonomy_uid") String taxonomyId, + @Path("term_id") String termId, + @QueryMap Map queryParams); + @GET("taxonomies/$all/terms") Call searchTerm( @HeaderMap HashMap headers, diff --git a/src/main/java/com/contentstack/cms/stack/Terms.java b/src/main/java/com/contentstack/cms/stack/Terms.java index 8f3a3a0c..021b09b3 100644 --- a/src/main/java/com/contentstack/cms/stack/Terms.java +++ b/src/main/java/com/contentstack/cms/stack/Terms.java @@ -278,6 +278,37 @@ public Call reorder(@NotNull String termUid, @NotNull JSONObject b return this.taxonomyService.reorder(this.headers, this.taxonomyId, termUid, this.params, body); } + /** + * Localize a term into the given locale. + * + * @param termUid the term to localize + * @param body the request body, e.g. {@code {"term": {"uid": "...", "name": "..."}}} + * @param locale the target locale, e.g. {@code hi-in} + * @return instance of Call

    Example

     {@code
    +     * Stack stack = new Contentstack.Builder().build().stack(headers);
    +     * Term term = stack.taxonomy("taxonomyId").terms().localize("termId", body, "hi-in");
    +     * } 
    + */ + public Call localize(@NotNull String termUid, @NotNull JSONObject body, @NotNull String locale) { + this.params.put("locale", locale); + return this.taxonomyService.localizeTerm(this.headers, this.taxonomyId, termUid, this.params, body); + } + + /** + * Unlocalize a term from the given locale. + * + * @param termUid the term to unlocalize + * @param locale the locale to remove, e.g. {@code hi-in} + * @return instance of Call

    Example

     {@code
    +     * Stack stack = new Contentstack.Builder().build().stack(headers);
    +     * Term term = stack.taxonomy("taxonomyId").terms().unlocalize("termId", "hi-in");
    +     * } 
    + */ + public Call unlocalize(@NotNull String termUid, @NotNull String locale) { + this.params.put("locale", locale); + return this.taxonomyService.unlocalizeTerm(this.headers, this.taxonomyId, termUid, this.params); + } + /** * Search call. diff --git a/src/test/java/com/contentstack/cms/UnitTestSuite.java b/src/test/java/com/contentstack/cms/UnitTestSuite.java index 9a54d743..ef67e11a 100644 --- a/src/test/java/com/contentstack/cms/UnitTestSuite.java +++ b/src/test/java/com/contentstack/cms/UnitTestSuite.java @@ -7,6 +7,7 @@ import com.contentstack.cms.stack.GlobalFieldUnitTests; import com.contentstack.cms.stack.LocaleUnitTest; import com.contentstack.cms.stack.ReleaseUnitTest; +import com.contentstack.cms.stack.TaxonomyTest; import org.junit.platform.runner.JUnitPlatform; import org.junit.platform.suite.api.SelectClasses; import org.junit.runner.RunWith; @@ -32,7 +33,8 @@ EnvironmentUnitTest.class, GlobalFieldUnitTests.class, LocaleUnitTest.class, - ReleaseUnitTest.class + ReleaseUnitTest.class, + TaxonomyTest.class }) public class UnitTestSuite { } diff --git a/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java b/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java index c1d61f3c..a8d762dd 100644 --- a/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java +++ b/src/test/java/com/contentstack/cms/stack/TaxonomyAPITest.java @@ -215,6 +215,96 @@ void descendantsTerm(){ Assertions.assertNull(request.body()); Assertions.assertEquals("include_count=true", request.url().encodedQuery()); } + @Test + void publishTaxonomy() { + JSONObject publishBody = Utils.readJson("mocktaxonomy/publish.json"); + Request request = taxonomy.publish(publishBody).request(); + Assertions.assertEquals("POST", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals("publish", request.url().pathSegments().get(2)); + Assertions.assertNotNull(request.body()); + } + + @Test + void unpublishTaxonomy() { + JSONObject unpublishBody = Utils.readJson("mocktaxonomy/publish.json"); + Request request = taxonomy.unpublish(unpublishBody).request(); + Assertions.assertEquals("POST", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals("unpublish", request.url().pathSegments().get(2)); + Assertions.assertNotNull(request.body()); + } + + @Test + void localizeTaxonomy() { + Taxonomy uidTaxonomy = new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build() + .stack(API_KEY, MANAGEMENT_TOKEN).taxonomy(_uid); + JSONObject localizeBody = Utils.readJson("mocktaxonomy/localize.json"); + Request request = uidTaxonomy.localize(localizeBody, "hi-in").request(); + Assertions.assertEquals("POST", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("locale=hi-in", request.url().encodedQuery()); + Assertions.assertNotNull(request.body()); + } + + @Test + void unlocalizeTaxonomy() { + Taxonomy uidTaxonomy = new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build() + .stack(API_KEY, MANAGEMENT_TOKEN).taxonomy(_uid); + Request request = uidTaxonomy.unlocalize("hi-in").request(); + Assertions.assertEquals("DELETE", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("locale=hi-in", request.url().encodedQuery()); + Assertions.assertNull(request.body()); + } + + @Test + void localizeTerm() throws IOException { + terms.clearParams(); + JSONObject localizeBody = Utils.readJson("mocktaxonomy/localizeTerm.json"); + Request request = terms.localize("india", localizeBody, "hi-in").request(); + Assertions.assertEquals("POST", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertEquals("india", request.url().pathSegments().get(4)); + Assertions.assertEquals("locale=hi-in", request.url().encodedQuery()); + Assertions.assertNotNull(request.body()); + } + + @Test + void unlocalizeTerm() { + terms.clearParams(); + Request request = terms.unlocalize("india", "hi-in").request(); + Assertions.assertEquals("DELETE", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertEquals("india", request.url().pathSegments().get(4)); + Assertions.assertEquals("locale=hi-in", request.url().encodedQuery()); + Assertions.assertNull(request.body()); + } + @Test void moveTerms(){ terms.clearParams(); diff --git a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java index 693a7db6..d793ccbf 100644 --- a/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java +++ b/src/test/java/com/contentstack/cms/stack/TaxonomyTest.java @@ -2,6 +2,7 @@ import com.contentstack.cms.Contentstack; import com.contentstack.cms.TestClient; +import com.contentstack.cms.core.ErrorMessages; import com.contentstack.cms.core.Util; import okhttp3.Request; import okhttp3.ResponseBody; @@ -12,11 +13,12 @@ import retrofit2.Response; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; @Tag("unit") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class TaxonomyTest { +public class TaxonomyTest { protected static String API_KEY = TestClient.API_KEY; protected static String _uid = TestClient.AUTHTOKEN; @@ -277,6 +279,43 @@ void testAncestorsTerm() { Assertions.assertEquals("include_children_count=false&include_referenced_entries_count=true", request.url().encodedQuery()); } + @Test + void testLocalizeTerm() { + terms.clearParams(); + JSONObject termDetails = new JSONObject(); + termDetails.put("uid", "term_1"); + termDetails.put("name", "Term 1 Hindi"); + JSONObject term = new JSONObject(); + term.put("term", termDetails); + Request request = terms.localize("term_1", term, "hi-in").request(); + Assertions.assertEquals("POST", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertEquals("term_1", request.url().pathSegments().get(4)); + Assertions.assertEquals("locale=hi-in", request.url().encodedQuery()); + Assertions.assertNotNull(request.body()); + } + + @Test + void testUnlocalizeTerm() { + terms.clearParams(); + Request request = terms.unlocalize("term_1", "hi-in").request(); + Assertions.assertEquals("DELETE", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals(5, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("terms", request.url().pathSegments().get(3)); + Assertions.assertEquals("term_1", request.url().pathSegments().get(4)); + Assertions.assertEquals("locale=hi-in", request.url().encodedQuery()); + Assertions.assertNull(request.body()); + } + @Test void findTestAPI() throws IOException { Taxonomy taxonomy = new Contentstack.Builder() @@ -289,6 +328,113 @@ void findTestAPI() throws IOException { System.out.println(response); } + + private static Taxonomy newTaxonomy() { + HashMap headers = new HashMap<>(); + headers.put(Util.API_KEY, API_KEY); + headers.put(Util.AUTHORIZATION, MANAGEMENT_TOKEN); + return new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build().stack(headers).taxonomy(); + } + + private static Taxonomy newTaxonomy(String uid) { + HashMap headers = new HashMap<>(); + headers.put(Util.API_KEY, API_KEY); + headers.put(Util.AUTHORIZATION, MANAGEMENT_TOKEN); + return new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build().stack(headers).taxonomy(uid); + } + + @Test + void publishTest() { + JSONObject publishBody = new JSONObject(); + publishBody.put("locales", Collections.singletonList("en-us")); + publishBody.put("environments", Collections.singletonList("production")); + Request request = taxonomy.publish(publishBody).request(); + Assertions.assertEquals("POST", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals("publish", request.url().pathSegments().get(2)); + Assertions.assertNotNull(request.body()); + } + + @Test + void publishTestWithBranchHeader() { + Taxonomy localTaxonomy = newTaxonomy(); + localTaxonomy.addHeader("branch", "dev"); + JSONObject publishBody = new JSONObject(); + publishBody.put("locales", Collections.singletonList("en-us")); + Request request = localTaxonomy.publish(publishBody).request(); + Assertions.assertEquals("dev", request.header("branch")); + } + + @Test + void unpublishTest() { + JSONObject unpublishBody = new JSONObject(); + unpublishBody.put("locales", Collections.singletonList("en-us")); + unpublishBody.put("environments", Collections.singletonList("production")); + Request request = taxonomy.unpublish(unpublishBody).request(); + Assertions.assertEquals("POST", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals("unpublish", request.url().pathSegments().get(2)); + Assertions.assertNotNull(request.body()); + } + + @Test + void localizeTest() { + Taxonomy uidTaxonomy = newTaxonomy(_uid); + JSONObject taxonomyDetails = new JSONObject(); + taxonomyDetails.put("uid", _uid); + taxonomyDetails.put("name", "Taxonomy 1"); + JSONObject localizeBody = new JSONObject(); + localizeBody.put("taxonomy", taxonomyDetails); + Request request = uidTaxonomy.localize(localizeBody, "hi-in").request(); + Assertions.assertEquals("POST", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("locale=hi-in", request.url().encodedQuery()); + Assertions.assertNotNull(request.body()); + } + + @Test + void localizeTestWithoutUidThrows() { + JSONObject taxonomyDetails = new JSONObject(); + taxonomyDetails.put("uid", _uid); + taxonomyDetails.put("name", "Taxonomy 1"); + JSONObject localizeBody = new JSONObject(); + localizeBody.put("taxonomy", taxonomyDetails); + NullPointerException exception = Assertions.assertThrows(NullPointerException.class, + () -> taxonomy.localize(localizeBody, "hi-in")); + Assertions.assertEquals(ErrorMessages.TAXONOMY_UID_REQUIRED, exception.getMessage()); + } + + @Test + void unlocalizeTest() { + Taxonomy uidTaxonomy = newTaxonomy(_uid); + Request request = uidTaxonomy.unlocalize("hi-in").request(); + Assertions.assertEquals("DELETE", request.method()); + Assertions.assertTrue(request.url().isHttps()); + Assertions.assertEquals(3, request.url().pathSegments().size()); + Assertions.assertEquals("v3", request.url().pathSegments().get(0)); + Assertions.assertEquals("taxonomies", request.url().pathSegments().get(1)); + Assertions.assertEquals(_uid, request.url().pathSegments().get(2)); + Assertions.assertEquals("locale=hi-in", request.url().encodedQuery()); + Assertions.assertNull(request.body()); + } + + @Test + void unlocalizeTestWithoutUidThrows() { + NullPointerException exception = Assertions.assertThrows(NullPointerException.class, + () -> taxonomy.unlocalize("hi-in")); + Assertions.assertEquals(ErrorMessages.TAXONOMY_UID_REQUIRED, exception.getMessage()); + } + @Test void queryFiltersOnTaxonomy() { Taxonomy taxonomy = new Contentstack.Builder() diff --git a/src/test/resources/mocktaxonomy/localize.json b/src/test/resources/mocktaxonomy/localize.json new file mode 100644 index 00000000..18ae725d --- /dev/null +++ b/src/test/resources/mocktaxonomy/localize.json @@ -0,0 +1,7 @@ +{ + "taxonomy": { + "uid": "sample_one", + "name": "Sample One in Hindi", + "description": "Description for the sample one taxonomy in Hindi." + } + } \ No newline at end of file diff --git a/src/test/resources/mocktaxonomy/localizeTerm.json b/src/test/resources/mocktaxonomy/localizeTerm.json new file mode 100644 index 00000000..b2d516c9 --- /dev/null +++ b/src/test/resources/mocktaxonomy/localizeTerm.json @@ -0,0 +1,8 @@ +{ + "term": { + "uid": "india", + "name": "India in Hindi", + "parent_uid": null, + "order": 1 + } +} \ No newline at end of file diff --git a/src/test/resources/mocktaxonomy/publish.json b/src/test/resources/mocktaxonomy/publish.json new file mode 100644 index 00000000..931580a1 --- /dev/null +++ b/src/test/resources/mocktaxonomy/publish.json @@ -0,0 +1,6 @@ + +{ + "locales": ["en-us"], + "environments": ["production"], + "items": [{ "uid": "sample_one" }] + } \ No newline at end of file