From 4eb3c84121c775f41600cbb3c77862744bf23c56 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 12 May 2026 17:21:51 +0530 Subject: [PATCH 1/3] feat: entry variants branch support --- .../contentstack/cms/core/ErrorMessages.java | 1 + .../java/com/contentstack/cms/core/Util.java | 14 ++ .../com/contentstack/cms/stack/Entry.java | 131 +++++++++++++++++- .../contentstack/cms/stack/EntryService.java | 52 ++++++- 4 files changed, 195 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/contentstack/cms/core/ErrorMessages.java b/src/main/java/com/contentstack/cms/core/ErrorMessages.java index 477809fa..e50a888e 100644 --- a/src/main/java/com/contentstack/cms/core/ErrorMessages.java +++ b/src/main/java/com/contentstack/cms/core/ErrorMessages.java @@ -37,6 +37,7 @@ private ErrorMessages() { 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 VARIANT_UID_REQUIRED = "Variant UID is required. Provide a valid Variant 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/core/Util.java b/src/main/java/com/contentstack/cms/core/Util.java index 9e2d5130..7a0ab462 100644 --- a/src/main/java/com/contentstack/cms/core/Util.java +++ b/src/main/java/com/contentstack/cms/core/Util.java @@ -39,6 +39,20 @@ public class Util { public static final String AUTHTOKEN = "authtoken"; public static final String EARLY_ACCESS_HEADER = "x-header-ea"; public static final String BRANCH = "branch"; + + /** + * Request header to fetch a base entry with a specific entry variant applied (personalization). + */ + public static final String X_CS_VARIANT_UID = "x-cs-variant-uid"; + + /** + * Required on publish/unpublish when the request body includes {@code entry.variants} (entry variant flows). + */ + public static final String API_VERSION = "api_version"; + + /** Value for {@link #API_VERSION} when publishing or unpublishing entry variants per Content Management API. */ + public static final String API_VERSION_ENTRY_VARIANTS_PUBLISH = "3.2"; + public static final String X_USER_AGENT = "X-User-Agent"; public static final String USER_AGENT = "User-Agent"; public static final String CONTENT_TYPE = "Content-Type"; diff --git a/src/main/java/com/contentstack/cms/stack/Entry.java b/src/main/java/com/contentstack/cms/stack/Entry.java index 1f74c5dc..0a074390 100644 --- a/src/main/java/com/contentstack/cms/stack/Entry.java +++ b/src/main/java/com/contentstack/cms/stack/Entry.java @@ -1,6 +1,7 @@ package com.contentstack.cms.stack; import com.contentstack.cms.core.ErrorMessages; +import com.contentstack.cms.core.Util; import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; @@ -68,6 +69,37 @@ private void validateCT() { Objects.requireNonNull(this.contentTypeUid, ERROR_CT_UID); } + private void validateVariantUid(@NotNull String variantUid) { + Objects.requireNonNull(variantUid, ErrorMessages.VARIANT_UID_REQUIRED); + if (variantUid.isEmpty()) { + throw new IllegalArgumentException(ErrorMessages.VARIANT_UID_REQUIRED); + } + } + + /** + * Sets the branch header for requests scoped to a stack branch (e.g. development). + * + * @param branchUid branch UID or alias target branch UID + * @return this entry instance for chaining + */ + public Entry addBranch(@NotNull String branchUid) { + this.headers.put(Util.BRANCH, branchUid); + return this; + } + + /** + * Sets {@value Util#X_CS_VARIANT_UID} for {@link #fetch()} / {@link #fetchAsPojo()} to retrieve the base entry with a + * specific variant applied (personalization). + * + * @param variantUid Content variant UID (e.g. {@code cs…}) + * @return this entry instance for chaining + */ + public Entry withAppliedVariantUid(@NotNull String variantUid) { + validateVariantUid(variantUid); + this.headers.put(Util.X_CS_VARIANT_UID, variantUid); + return this; + } + /** * Sets header for the request * @@ -710,6 +742,75 @@ public Call importExisting() { return this.service.importExisting(this.headers, this.contentTypeUid, this.entryUid, this.params); } + /** + * Retrieves all entry variants for this entry. + *

+ * Use {@link #addParam(String, Object)} for optional queries such as {@code locale}, {@code include_workflow}, + * {@link #addBranch(String)} or stack-level branch header for branch-scoped stacks. + * + * @return Retrofit call for GET …/entries/{entry_uid}/variants + * @see Get all entry variants + */ + public Call fetchEntryVariants() { + validateCT(); + validateEntry(); + return this.service.fetchEntryVariants(this.headers, this.contentTypeUid, this.entryUid, this.params); + } + + /** + * Retrieves a single entry variant. + * + * @param variantUid variant UID path segment + * @return Retrofit call for GET …/variants/{variant_uid} + */ + public Call fetchEntryVariant(@NotNull String variantUid) { + validateCT(); + validateEntry(); + validateVariantUid(variantUid); + return this.service.fetchEntryVariant(this.headers, this.contentTypeUid, this.entryUid, variantUid, this.params); + } + + /** + * Creates an entry variant. Uses PUT …/variants/{variant_uid} (CMA upsert — same URL as {@link #updateEntryVariant}). + * + * @param variantUid variant UID path segment + * @param requestBody JSON body per API (typically wraps fields under {@code entry}) + * @see Create Entry Variant + */ + public Call createEntryVariant(@NotNull String variantUid, @NotNull JSONObject requestBody) { + validateCT(); + validateEntry(); + validateVariantUid(variantUid); + return this.service.createEntryVariant(this.headers, this.contentTypeUid, this.entryUid, variantUid, this.params, + requestBody); + } + + /** + * Updates an entry variant. Same HTTP request shape as create (PUT upsert). + * + * @see Update Entry Variant + */ + public Call updateEntryVariant(@NotNull String variantUid, @NotNull JSONObject requestBody) { + validateCT(); + validateEntry(); + validateVariantUid(variantUid); + return this.service.updateEntryVariant(this.headers, this.contentTypeUid, this.entryUid, variantUid, this.params, + requestBody); + } + + /** + * Deletes an entry variant. + * + * @param variantUid variant UID path segment + * @return Retrofit call for DELETE …/variants/{variant_uid} + */ + public Call deleteEntryVariant(@NotNull String variantUid) { + validateCT(); + validateEntry(); + validateVariantUid(variantUid); + return this.service.deleteEntryVariant(this.headers, this.contentTypeUid, this.entryUid, variantUid, this.params); + } + /** * To Publish an entry request lets you publish an entry either immediately or * schedule it for a later date/time. @@ -752,7 +853,22 @@ public Call importExisting() { public Call publish(@NotNull JSONObject requestBody) { validateCT(); validateEntry(); - return this.service.publish(this.headers, this.contentTypeUid, this.entryUid, requestBody); + return this.service.publish(this.headers, this.contentTypeUid, this.entryUid, this.params, requestBody); + } + + /** + * Publishes entry variants using the entry publish endpoint with {@code entry.variants} in the body. + * Sends header {@value Util#API_VERSION}={@value Util#API_VERSION_ENTRY_VARIANTS_PUBLISH} unless already set on this entry instance. + * Use {@link #addParam(String, Object)} for optional {@code locale} query parameter; use {@link #addBranch(String)} for branch scope. + * + * @param requestBody full publish payload including {@code entry}, {@code locale}, etc. + */ + public Call publishEntryVariants(@NotNull JSONObject requestBody) { + validateCT(); + validateEntry(); + HashMap publishHeaders = new HashMap<>(this.headers); + publishHeaders.putIfAbsent(Util.API_VERSION, Util.API_VERSION_ENTRY_VARIANTS_PUBLISH); + return this.service.publish(publishHeaders, this.contentTypeUid, this.entryUid, this.params, requestBody); } /** @@ -816,9 +932,20 @@ public Call publishWithReference(@NotNull JSONObject requestBody) public Call unpublish(@NotNull JSONObject requestBody) { validateCT(); validateEntry(); - return this.service.unpublish(this.headers, this.contentTypeUid, this.entryUid, requestBody); + return this.service.unpublish(this.headers, this.contentTypeUid, this.entryUid, this.params, requestBody); } + /** + * Unpublishes entry variants via the entry unpublish endpoint with {@code entry.variants} in the body. + * Sends header {@value Util#API_VERSION}={@value Util#API_VERSION_ENTRY_VARIANTS_PUBLISH} unless already set. + */ + public Call unpublishEntryVariants(@NotNull JSONObject requestBody) { + validateCT(); + validateEntry(); + HashMap unpublishHeaders = new HashMap<>(this.headers); + unpublishHeaders.putIfAbsent(Util.API_VERSION, Util.API_VERSION_ENTRY_VARIANTS_PUBLISH); + return this.service.unpublish(unpublishHeaders, this.contentTypeUid, this.entryUid, this.params, requestBody); + } /** * Get instance of taxonomy search filter class instance through which we can query on taxonomy based on content type diff --git a/src/main/java/com/contentstack/cms/stack/EntryService.java b/src/main/java/com/contentstack/cms/stack/EntryService.java index 8c5c82fb..cfa6e742 100644 --- a/src/main/java/com/contentstack/cms/stack/EntryService.java +++ b/src/main/java/com/contentstack/cms/stack/EntryService.java @@ -5,7 +5,6 @@ import retrofit2.Call; import retrofit2.http.*; -import java.util.List; import java.util.Map; public interface EntryService { @@ -147,6 +146,7 @@ Call publish( @HeaderMap Map headers, @Path("content_type_uid") String contentTypeUid, @Path("entry_uid") String entryUid, + @QueryMap(encoded = true) Map queryParameters, @Body JSONObject requestBody); @POST("bulk/publish?x-bulk-action=publish") @@ -160,8 +160,58 @@ Call unpublish( @HeaderMap Map headers, @Path("content_type_uid") String contentTypeUid, @Path("entry_uid") String entryUid, + @QueryMap(encoded = true) Map queryParameters, @Body JSONObject requestBody); + @GET("content_types/{content_type_uid}/entries/{entry_uid}/variants") + Call fetchEntryVariants( + @HeaderMap Map headers, + @Path("content_type_uid") String contentTypeUid, + @Path("entry_uid") String entryUid, + @QueryMap(encoded = true) Map queryParameters); + + @GET("content_types/{content_type_uid}/entries/{entry_uid}/variants/{variant_uid}") + Call fetchEntryVariant( + @HeaderMap Map headers, + @Path("content_type_uid") String contentTypeUid, + @Path("entry_uid") String entryUid, + @Path("variant_uid") String variantUid, + @QueryMap(encoded = true) Map queryParameters); + + /** + * Create entry variant (PUT …/variants/{variant_uid}). Same HTTP contract as update — CMA upserts on this path. + */ + @Headers("Content-Type: application/json") + @PUT("content_types/{content_type_uid}/entries/{entry_uid}/variants/{variant_uid}") + Call createEntryVariant( + @HeaderMap Map headers, + @Path("content_type_uid") String contentTypeUid, + @Path("entry_uid") String entryUid, + @Path("variant_uid") String variantUid, + @QueryMap(encoded = true) Map queryParameters, + @Body JSONObject requestBody); + + /** + * Update entry variant — delegates to {@link #createEntryVariant}; API uses one PUT upsert for both operations. + */ + default Call updateEntryVariant( + Map headers, + String contentTypeUid, + String entryUid, + String variantUid, + Map queryParameters, + JSONObject requestBody) { + return createEntryVariant(headers, contentTypeUid, entryUid, variantUid, queryParameters, requestBody); + } + + @DELETE("content_types/{content_type_uid}/entries/{entry_uid}/variants/{variant_uid}") + Call deleteEntryVariant( + @HeaderMap Map headers, + @Path("content_type_uid") String contentTypeUid, + @Path("entry_uid") String entryUid, + @Path("variant_uid") String variantUid, + @QueryMap(encoded = true) Map queryParameters); + @GET("content_types/{content_type_uid}/entries") Call filterTaxonomy( @HeaderMap Map headers, From baa3c5e81bc6a47ee6153dc9a55db6fcab120937 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Tue, 12 May 2026 18:06:00 +0530 Subject: [PATCH 2/3] enh: improve branch handling for entry variants and stack operations --- .../com/contentstack/cms/Contentstack.java | 8 ++- .../com/contentstack/cms/stack/Entry.java | 59 +++++++++++++++++-- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/contentstack/cms/Contentstack.java b/src/main/java/com/contentstack/cms/Contentstack.java index ebe847b3..e8018325 100644 --- a/src/main/java/com/contentstack/cms/Contentstack.java +++ b/src/main/java/com/contentstack/cms/Contentstack.java @@ -439,16 +439,20 @@ public Stack stack(@NotNull String key) { * property). Within a stack, you can create content structures, content * entries, users, etc. related to the project *

+ * Passing {@code branch} sets the {@value com.contentstack.cms.core.Util#BRANCH} request header for this stack. + * That header applies to entries and entry-variant operations ({@code …/variants/…}) unless overridden per instance, + * e.g. {@link com.contentstack.cms.stack.Entry#addBranch(String)} replaces {@code branch} for that entry only. + *

* Example * *

      * Contentstack client = new Contentstack.Builder().build();
-     * Stack org = client.stack();
+     * Stack stack = client.stack("API_KEY", "MANAGEMENT_TOKEN", "feature-branch");
      * 
* * @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 branch branch UID or alias for the {@value com.contentstack.cms.core.Util#BRANCH} header * @return the stack instance */ public Stack stack(@NotNull String apiKey, @NotNull String managementToken, @NotNull String branch) { diff --git a/src/main/java/com/contentstack/cms/stack/Entry.java b/src/main/java/com/contentstack/cms/stack/Entry.java index 0a074390..c7399de0 100644 --- a/src/main/java/com/contentstack/cms/stack/Entry.java +++ b/src/main/java/com/contentstack/cms/stack/Entry.java @@ -6,6 +6,7 @@ import com.contentstack.cms.BaseImplementation; import okhttp3.ResponseBody; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.json.simple.JSONObject; import retrofit2.Call; import retrofit2.Retrofit; @@ -76,8 +77,23 @@ private void validateVariantUid(@NotNull String variantUid) { } } + /** + * Header map for a variant request when {@code branchUid} is supplied for this call only (does not mutate {@link #headers}). + * Null or blank {@code branchUid} keeps {@link #headers} as-is (stack / {@link #addBranch(String)} behavior). + */ + private Map variantHeadersWithOptionalBranch(@Nullable String branchUid) { + if (branchUid == null || branchUid.isEmpty()) { + return this.headers; + } + HashMap copy = new HashMap<>(this.headers); + copy.put(Util.BRANCH, branchUid); + return copy; + } + /** * Sets the branch header for requests scoped to a stack branch (e.g. development). + * Overrides the branch set via {@link com.contentstack.cms.Contentstack#stack(String, String, String)} for this + * {@link Entry} instance only (including entry-variant CRUD, publish, and unpublish). Uses header key {@value Util#BRANCH}. * * @param branchUid branch UID or alias target branch UID * @return this entry instance for chaining @@ -745,8 +761,9 @@ public Call importExisting() { /** * Retrieves all entry variants for this entry. *

- * Use {@link #addParam(String, Object)} for optional queries such as {@code locale}, {@code include_workflow}, - * {@link #addBranch(String)} or stack-level branch header for branch-scoped stacks. + * Use {@link #addParam(String, Object)} for optional queries such as {@code locale}, {@code include_workflow}. + * Branch scope: stack {@value Util#BRANCH} from {@link com.contentstack.cms.Contentstack#stack(String, String, String)} + * is forwarded; {@link #addBranch(String)} overrides for this entry only. * * @return Retrofit call for GET …/entries/{entry_uid}/variants * @see Get all entry variants @@ -758,20 +775,40 @@ public Call fetchEntryVariants() { } /** - * Retrieves a single entry variant. + * Retrieves a single entry variant using {@link #headers} for {@value Util#BRANCH} (stack default and/or {@link #addBranch(String)}). * * @param variantUid variant UID path segment * @return Retrofit call for GET …/variants/{variant_uid} + * @see #fetchEntryVariant(String, String) */ public Call fetchEntryVariant(@NotNull String variantUid) { + return fetchEntryVariant(variantUid, null); + } + + /** + * Retrieves a single entry variant with an optional per-call {@value Util#BRANCH} override. + *

+ * When {@code branchUid} is non-blank, it replaces {@value Util#BRANCH} on this request only (stack and {@link #addBranch(String)} + * values are not mutated on the entry). When {@code branchUid} is {@code null} or blank, behavior matches {@link #fetchEntryVariant(String)}. + * {@link #withAppliedVariantUid(String)} ({@value Util#X_CS_VARIANT_UID}) is unrelated to branch. + * + * @param variantUid variant UID path segment + * @param branchUid optional branch UID or alias for this request only; {@code null} or empty to use entry headers + * @return Retrofit call for GET …/variants/{variant_uid} + */ + public Call fetchEntryVariant(@NotNull String variantUid, @Nullable String branchUid) { validateCT(); validateEntry(); validateVariantUid(variantUid); - return this.service.fetchEntryVariant(this.headers, this.contentTypeUid, this.entryUid, variantUid, this.params); + return this.service.fetchEntryVariant(variantHeadersWithOptionalBranch(branchUid), this.contentTypeUid, + this.entryUid, variantUid, this.params); } /** * Creates an entry variant. Uses PUT …/variants/{variant_uid} (CMA upsert — same URL as {@link #updateEntryVariant}). + *

+ * Branch scope: inherits stack {@value Util#BRANCH}; override with {@link #addBranch(String)} or {@link #addHeader(String, String)} + * ({@value Util#BRANCH}) on this entry. Variant personalization header {@value Util#X_CS_VARIANT_UID} is orthogonal. * * @param variantUid variant UID path segment * @param requestBody JSON body per API (typically wraps fields under {@code entry}) @@ -787,6 +824,9 @@ public Call createEntryVariant(@NotNull String variantUid, @NotNul /** * Updates an entry variant. Same HTTP request shape as create (PUT upsert). + *

+ * Branch scope: inherits stack {@value Util#BRANCH}; override with {@link #addBranch(String)} or {@link #addHeader(String, String)} + * ({@value Util#BRANCH}) on this entry. * * @see Update Entry Variant */ @@ -800,6 +840,9 @@ public Call updateEntryVariant(@NotNull String variantUid, @NotNul /** * Deletes an entry variant. + *

+ * Branch scope: inherits stack {@value Util#BRANCH}; override with {@link #addBranch(String)} or {@link #addHeader(String, String)} + * ({@value Util#BRANCH}) on this entry. * * @param variantUid variant UID path segment * @return Retrofit call for DELETE …/variants/{variant_uid} @@ -859,7 +902,10 @@ public Call publish(@NotNull JSONObject requestBody) { /** * Publishes entry variants using the entry publish endpoint with {@code entry.variants} in the body. * Sends header {@value Util#API_VERSION}={@value Util#API_VERSION_ENTRY_VARIANTS_PUBLISH} unless already set on this entry instance. - * Use {@link #addParam(String, Object)} for optional {@code locale} query parameter; use {@link #addBranch(String)} for branch scope. + * Use {@link #addParam(String, Object)} for optional {@code locale} query parameter. + *

+ * Branch scope: stack {@value Util#BRANCH} is copied into the publish request headers together with {@code api_version}; + * override with {@link #addBranch(String)} or {@link #addHeader(String, String)} ({@value Util#BRANCH}) on this entry. * * @param requestBody full publish payload including {@code entry}, {@code locale}, etc. */ @@ -938,6 +984,9 @@ public Call unpublish(@NotNull JSONObject requestBody) { /** * Unpublishes entry variants via the entry unpublish endpoint with {@code entry.variants} in the body. * Sends header {@value Util#API_VERSION}={@value Util#API_VERSION_ENTRY_VARIANTS_PUBLISH} unless already set. + *

+ * Branch scope: stack {@value Util#BRANCH} is forwarded; override with {@link #addBranch(String)} or {@link #addHeader(String, String)} + * ({@value Util#BRANCH}) on this entry. */ public Call unpublishEntryVariants(@NotNull JSONObject requestBody) { validateCT(); From 0f793e6e0aba5e327522b986a8bbc2674238c2f2 Mon Sep 17 00:00:00 2001 From: reeshika-h Date: Wed, 13 May 2026 18:25:52 +0530 Subject: [PATCH 3/3] feat: add entry variant tests and configuration updates --- pom.xml | 2 +- .../java/com/contentstack/cms/TestClient.java | 20 ++ .../com/contentstack/cms/UnitTestSuite.java | 2 + .../cms/stack/APISanityTestSuite.java | 1 + .../cms/stack/EntryVariantAPITest.java | 246 ++++++++++++++++++ .../cms/stack/EntryVariantUnitTest.java | 235 +++++++++++++++++ .../entry_variant/create_entry_variant.json | 15 ++ .../entry_variant/publish_entry_variant.json | 16 ++ .../unpublish_entry_variant.json | 12 + .../entry_variant/update_entry_variant.json | 17 ++ 10 files changed, 565 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/contentstack/cms/stack/EntryVariantAPITest.java create mode 100644 src/test/java/com/contentstack/cms/stack/EntryVariantUnitTest.java create mode 100644 src/test/resources/entry_variant/create_entry_variant.json create mode 100644 src/test/resources/entry_variant/publish_entry_variant.json create mode 100644 src/test/resources/entry_variant/unpublish_entry_variant.json create mode 100644 src/test/resources/entry_variant/update_entry_variant.json diff --git a/pom.xml b/pom.xml index c227e9ba..169f4b56 100644 --- a/pom.xml +++ b/pom.xml @@ -254,7 +254,7 @@ ${project.build.directory}/surefire-reports - + true true diff --git a/src/test/java/com/contentstack/cms/TestClient.java b/src/test/java/com/contentstack/cms/TestClient.java index 2503e39b..c6b1e0e5 100644 --- a/src/test/java/com/contentstack/cms/TestClient.java +++ b/src/test/java/com/contentstack/cms/TestClient.java @@ -25,6 +25,26 @@ public class TestClient { 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"; + + /** Content type UID for entry-variant tests (default {@code blog}). */ + public static final String ENTRY_VARIANT_CONTENT_TYPE_UID = + env.get("entryVariantContentTypeUid") != null ? env.get("entryVariantContentTypeUid") : "blog"; + + /** Stack branch for entry-variant tests (default {@code develop}). */ + public static final String ENTRY_VARIANT_BRANCH = + env.get("entryVariantBranch") != null ? env.get("entryVariantBranch") : "develop"; + + /** Locale query param for entry-variant tests (default {@code en-us}). */ + public static final String ENTRY_VARIANT_LOCALE = + env.get("entryVariantLocale") != null ? env.get("entryVariantLocale") : "en-us"; + + /** + * {@code true} when {@code apiKey} / {@code managementToken} were not loaded from {@code .env} (defaults apply). + */ + public static boolean isUsingDefaultStackCredentials() { + return env.get("apiKey") == null || env.get("managementToken") == null; + } + private static Contentstack instance; private static Stack stackInstance; diff --git a/src/test/java/com/contentstack/cms/UnitTestSuite.java b/src/test/java/com/contentstack/cms/UnitTestSuite.java index 97df7a51..912328f2 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.stack.EntryVariantUnitTest; import com.contentstack.cms.stack.EnvironmentUnitTest; import com.contentstack.cms.stack.GlobalFieldUnitTests; import com.contentstack.cms.stack.LocaleUnitTest; @@ -25,6 +26,7 @@ ContentstackUnitTest.class, // Stack module tests (only public classes) + EntryVariantUnitTest.class, EnvironmentUnitTest.class, GlobalFieldUnitTests.class, LocaleUnitTest.class, diff --git a/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java b/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java index 4b027193..14a514f1 100644 --- a/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java +++ b/src/test/java/com/contentstack/cms/stack/APISanityTestSuite.java @@ -22,6 +22,7 @@ GlobalFieldAPITest.class, VariantGroupAPITest.class, VariantGroupTest.class, + EntryVariantAPITest.class, ReleaseAPITest.class }) diff --git a/src/test/java/com/contentstack/cms/stack/EntryVariantAPITest.java b/src/test/java/com/contentstack/cms/stack/EntryVariantAPITest.java new file mode 100644 index 00000000..05a45f37 --- /dev/null +++ b/src/test/java/com/contentstack/cms/stack/EntryVariantAPITest.java @@ -0,0 +1,246 @@ +package com.contentstack.cms.stack; + +import com.contentstack.cms.TestClient; +import com.contentstack.cms.Utils; +import com.contentstack.cms.core.Util; + +import okhttp3.Request; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * Request-shape checks for entry-variant endpoints (no HTTP — same style as {@link ContentTypeAPITest}). + */ +@Tag("unit") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class EntryVariantAPITest { + + private static final String CREATE_VARIANT_BODY = "entry_variant/create_entry_variant.json"; + private static final String UPDATE_VARIANT_BODY = "entry_variant/update_entry_variant.json"; + private static final String PUBLISH_VARIANT_BODY = "entry_variant/publish_entry_variant.json"; + private static final String UNPUBLISH_VARIANT_BODY = "entry_variant/unpublish_entry_variant.json"; + + private final String contentTypeUid = TestClient.ENTRY_VARIANT_CONTENT_TYPE_UID; + private final String entryUid = "entry_uid"; + private final String variantUid = "variant_uid"; + private final String stackBranch = TestClient.ENTRY_VARIANT_BRANCH == null + ? "develop" + : TestClient.ENTRY_VARIANT_BRANCH.trim(); + + private Stack stack; + private int headerCount; + + @BeforeAll + void setUp() { + /* + * Fresh Stack from the client — avoids mutating {@link TestClient#getStack()} singleton headers + * (which broke other suites such as {@link ReleaseAPITest} when branch was added globally). + */ + stack = TestClient.getClient().stack( + TestClient.API_KEY, + TestClient.MANAGEMENT_TOKEN, + stackBranch); + headerCount = stack.headers.size(); + } + + private Entry freshEntry() { + return stack.contentType(contentTypeUid).entry(entryUid); + } + + private String variantPathSuffix() { + return "/v3/content_types/" + contentTypeUid + "/entries/" + entryUid + "/variants/" + variantUid; + } + + private String variantsCollectionPath() { + return "/v3/content_types/" + contentTypeUid + "/entries/" + entryUid + "/variants"; + } + + /** + * Publish/unpublish fixtures omit {@code variants[].uid}; inject placeholder for request body construction. + */ + @SuppressWarnings("unchecked") + private static void injectVariantUidIntoVariantsArray(JSONObject body, String uid) { + JSONObject ent = (JSONObject) body.get("entry"); + Assertions.assertNotNull(ent, "Body missing entry"); + JSONArray variants = (JSONArray) ent.get("variants"); + Assertions.assertNotNull(variants, "entry.variants missing"); + Assertions.assertFalse(variants.isEmpty(), "entry.variants empty"); + JSONObject v0 = (JSONObject) variants.get(0); + v0.put("uid", uid); + } + + @Test + @Order(1) + void createEntryVariant_requestShape() { + Entry entry = freshEntry(); + entry.clearParams(); + entry.addParam("locale", TestClient.ENTRY_VARIANT_LOCALE); + JSONObject body = Utils.readJson(CREATE_VARIANT_BODY); + Assertions.assertNotNull(body); + + Request req = entry.createEntryVariant(variantUid, body).request(); + Assertions.assertEquals(headerCount, req.headers().names().size()); + Assertions.assertEquals("PUT", req.method()); + Assertions.assertTrue(req.url().isHttps()); + Assertions.assertEquals(variantPathSuffix(), req.url().encodedPath()); + Assertions.assertEquals("locale=" + TestClient.ENTRY_VARIANT_LOCALE, req.url().encodedQuery()); + Assertions.assertEquals(stackBranch, req.header(Util.BRANCH)); + } + + @Test + @Order(2) + void updateEntryVariant_sameUrlAndMethodAsCreate() { + Entry entry = freshEntry(); + entry.clearParams(); + JSONObject createBody = Utils.readJson(CREATE_VARIANT_BODY); + JSONObject updateBody = Utils.readJson(UPDATE_VARIANT_BODY); + Assertions.assertNotNull(createBody); + Assertions.assertNotNull(updateBody); + + Request createReq = entry.createEntryVariant(variantUid, createBody).request(); + Entry entry2 = freshEntry(); + Request updateReq = entry2.updateEntryVariant(variantUid, updateBody).request(); + + Assertions.assertEquals(createReq.method(), updateReq.method()); + Assertions.assertEquals(createReq.url(), updateReq.url()); + } + + @Test + @Order(3) + void fetchSingleEntryVariant_requestShape() { + Entry entry = freshEntry(); + entry.clearParams(); + entry.addParam("locale", TestClient.ENTRY_VARIANT_LOCALE); + entry.addParam("include_publish_details", true); + + Request req = entry.fetchEntryVariant(variantUid).request(); + Assertions.assertEquals(headerCount, req.headers().names().size()); + Assertions.assertEquals("GET", req.method()); + Assertions.assertTrue(req.url().isHttps()); + Assertions.assertEquals(variantPathSuffix(), req.url().encodedPath()); + Assertions.assertNotNull(req.url().encodedQuery()); + Assertions.assertTrue(req.url().encodedQuery().contains("locale=" + TestClient.ENTRY_VARIANT_LOCALE)); + Assertions.assertTrue(req.url().encodedQuery().contains("include_publish_details=true")); + } + + @Test + @Order(4) + void fetchAllEntryVariants_requestShape() { + Entry entry = freshEntry(); + entry.clearParams(); + entry.addParam("locale", TestClient.ENTRY_VARIANT_LOCALE); + + Request req = entry.fetchEntryVariants().request(); + Assertions.assertEquals(headerCount, req.headers().names().size()); + Assertions.assertEquals("GET", req.method()); + Assertions.assertEquals(variantsCollectionPath(), req.url().encodedPath()); + Assertions.assertEquals("locale=" + TestClient.ENTRY_VARIANT_LOCALE, req.url().encodedQuery()); + } + + @Test + @Order(5) + void publishEntryVariants_requestShape() { + Entry entry = freshEntry(); + entry.clearParams(); + entry.addParam("locale", TestClient.ENTRY_VARIANT_LOCALE); + + JSONObject publishBody = Utils.readJson(PUBLISH_VARIANT_BODY); + Assertions.assertNotNull(publishBody); + injectVariantUidIntoVariantsArray(publishBody, variantUid); + + Request req = entry.publishEntryVariants(publishBody).request(); + Assertions.assertEquals("POST", req.method()); + Assertions.assertTrue(req.url().isHttps()); + Assertions.assertEquals( + "/v3/content_types/" + contentTypeUid + "/entries/" + entryUid + "/publish", + req.url().encodedPath()); + Assertions.assertEquals("locale=" + TestClient.ENTRY_VARIANT_LOCALE, req.url().encodedQuery()); + Assertions.assertEquals(Util.API_VERSION_ENTRY_VARIANTS_PUBLISH, req.header(Util.API_VERSION)); + } + + @Test + @Order(6) + void unpublishEntryVariants_requestShape() { + Entry entry = freshEntry(); + entry.clearParams(); + entry.addParam("locale", TestClient.ENTRY_VARIANT_LOCALE); + + JSONObject unpublishBody = Utils.readJson(UNPUBLISH_VARIANT_BODY); + Assertions.assertNotNull(unpublishBody); + injectVariantUidIntoVariantsArray(unpublishBody, variantUid); + + Request req = entry.unpublishEntryVariants(unpublishBody).request(); + Assertions.assertEquals("POST", req.method()); + Assertions.assertEquals( + "/v3/content_types/" + contentTypeUid + "/entries/" + entryUid + "/unpublish", + req.url().encodedPath()); + Assertions.assertEquals("locale=" + TestClient.ENTRY_VARIANT_LOCALE, req.url().encodedQuery()); + Assertions.assertEquals(Util.API_VERSION_ENTRY_VARIANTS_PUBLISH, req.header(Util.API_VERSION)); + } + + @Test + @Order(7) + void fetchSingleEntryVariant_implicitVsExplicitBranchHeader() { + Entry plain = freshEntry(); + plain.clearParams(); + plain.addParam("locale", TestClient.ENTRY_VARIANT_LOCALE); + + Request implicit = plain.fetchEntryVariant(variantUid).request(); + Assertions.assertEquals(stackBranch, implicit.header(Util.BRANCH)); + + Entry withPerCall = freshEntry(); + withPerCall.clearParams(); + withPerCall.addParam("locale", TestClient.ENTRY_VARIANT_LOCALE); + Request explicit = withPerCall.fetchEntryVariant(variantUid, stackBranch).request(); + Assertions.assertEquals(stackBranch, explicit.header(Util.BRANCH)); + Assertions.assertEquals(implicit.url(), explicit.url()); + } + + @Test + @Order(8) + void fetchSingleEntryVariant_afterAddBranch() { + Entry entry = freshEntry(); + entry.clearParams(); + entry.addBranch(stackBranch); + entry.addParam("locale", TestClient.ENTRY_VARIANT_LOCALE); + + Request req = entry.fetchEntryVariant(variantUid).request(); + Assertions.assertEquals(stackBranch, req.header(Util.BRANCH)); + Assertions.assertEquals(variantPathSuffix(), req.url().encodedPath()); + } + + @Test + @Order(9) + void fetchSingleEntryVariant_withPerCallBranchOverload() { + Entry entry = freshEntry(); + entry.clearParams(); + entry.addParam("locale", TestClient.ENTRY_VARIANT_LOCALE); + + Request req = entry.fetchEntryVariant(variantUid, stackBranch).request(); + Assertions.assertEquals(stackBranch, req.header(Util.BRANCH)); + Assertions.assertEquals("GET", req.method()); + Assertions.assertEquals(variantPathSuffix(), req.url().encodedPath()); + } + + @Test + @Order(10) + void deleteEntryVariant_requestShape() { + Entry entry = freshEntry(); + entry.clearParams(); + entry.addParam("locale", TestClient.ENTRY_VARIANT_LOCALE); + + Request req = entry.deleteEntryVariant(variantUid).request(); + Assertions.assertEquals("DELETE", req.method()); + Assertions.assertEquals(variantPathSuffix(), req.url().encodedPath()); + Assertions.assertEquals("locale=" + TestClient.ENTRY_VARIANT_LOCALE, req.url().encodedQuery()); + } +} diff --git a/src/test/java/com/contentstack/cms/stack/EntryVariantUnitTest.java b/src/test/java/com/contentstack/cms/stack/EntryVariantUnitTest.java new file mode 100644 index 00000000..acb67fe5 --- /dev/null +++ b/src/test/java/com/contentstack/cms/stack/EntryVariantUnitTest.java @@ -0,0 +1,235 @@ +package com.contentstack.cms.stack; + +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 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.Test; + +/** + * Request-shape checks for entry variants (no HTTP — safe without dev18 / prod variant APIs). + */ +@Tag("unit") +public class EntryVariantUnitTest { + + private static final String CREATE_VARIANT_BODY = "entry_variant/create_entry_variant.json"; + private static final String UPDATE_VARIANT_BODY = "entry_variant/update_entry_variant.json"; + + private static Entry entry; + + @BeforeAll + static void setup() { + Contentstack cms = new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build(); + Stack stack = cms.stack(TestClient.API_KEY, TestClient.MANAGEMENT_TOKEN); + entry = stack.contentType("blog").entry("entryUid123"); + } + + @Test + void createEntryVariant_requestIsPutWithVariantSegment() { + entry.clearParams(); + entry.addParam("locale", "en-us"); + JSONObject body = Utils.readJson(CREATE_VARIANT_BODY); + Assertions.assertNotNull(body, "Missing " + CREATE_VARIANT_BODY); + + Request req = entry.createEntryVariant("variantUid456", body).request(); + Assertions.assertEquals("PUT", req.method()); + Assertions.assertEquals( + "/v3/content_types/blog/entries/entryUid123/variants/variantUid456", + req.url().encodedPath()); + Assertions.assertEquals("locale=en-us", req.url().encodedQuery()); + } + + @Test + void updateEntryVariant_samePathAndMethodAsCreate() { + entry.clearParams(); + JSONObject createBody = Utils.readJson(CREATE_VARIANT_BODY); + JSONObject updateBody = Utils.readJson(UPDATE_VARIANT_BODY); + Assertions.assertNotNull(createBody, "Missing " + CREATE_VARIANT_BODY); + Assertions.assertNotNull(updateBody, "Missing " + UPDATE_VARIANT_BODY); + + Request createReq = entry.createEntryVariant("sameUid", createBody).request(); + Request updateReq = entry.updateEntryVariant("sameUid", updateBody).request(); + + Assertions.assertEquals(createReq.method(), updateReq.method()); + Assertions.assertEquals(createReq.url(), updateReq.url()); + } + + @Test + void fetchEntryVariants_requestIsGetOnVariantsCollection() { + entry.clearParams(); + Request req = entry.fetchEntryVariants().request(); + Assertions.assertEquals("GET", req.method()); + Assertions.assertEquals( + "/v3/content_types/blog/entries/entryUid123/variants", + req.url().encodedPath()); + } + + @Test + void fetchEntryVariant_requestIsGetOnSingleVariant() { + entry.clearParams(); + Request req = entry.fetchEntryVariant("v1").request(); + Assertions.assertEquals( + "/v3/content_types/blog/entries/entryUid123/variants/v1", + req.url().encodedPath()); + } + + @Test + void publishEntryVariants_addsApiVersionHeaderWhenNotAlreadySet() { + entry.clearParams(); + JSONObject body = new JSONObject(); + Request req = entry.publishEntryVariants(body).request(); + Assertions.assertEquals(Util.API_VERSION_ENTRY_VARIANTS_PUBLISH, req.header(Util.API_VERSION)); + } + + @Test + void unpublishEntryVariants_addsApiVersionHeaderWhenNotAlreadySet() { + entry.clearParams(); + JSONObject body = new JSONObject(); + Request req = entry.unpublishEntryVariants(body).request(); + Assertions.assertEquals(Util.API_VERSION_ENTRY_VARIANTS_PUBLISH, req.header(Util.API_VERSION)); + } + + @Test + void deleteEntryVariant_requestIsDeleteOnVariantPath() { + entry.clearParams(); + Request req = entry.deleteEntryVariant("toRemove").request(); + Assertions.assertEquals("DELETE", req.method()); + Assertions.assertEquals( + "/v3/content_types/blog/entries/entryUid123/variants/toRemove", + req.url().encodedPath()); + } + + @Test + void variantOperations_forwardStackBranchFromThreeArgStack() { + Contentstack cms = new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build(); + Stack stack = cms.stack(TestClient.API_KEY, TestClient.MANAGEMENT_TOKEN, "feature-branch"); + Entry e = stack.contentType("blog").entry("entryUid123"); + e.clearParams(); + JSONObject body = Utils.readJson(CREATE_VARIANT_BODY); + Assertions.assertNotNull(body, "Missing " + CREATE_VARIANT_BODY); + + Assertions.assertEquals( + "feature-branch", + e.createEntryVariant("variantUid456", body).request().header(Util.BRANCH)); + Assertions.assertEquals( + "feature-branch", + e.fetchEntryVariants().request().header(Util.BRANCH)); + Assertions.assertEquals( + "feature-branch", + e.fetchEntryVariant("v1").request().header(Util.BRANCH)); + Assertions.assertEquals( + "feature-branch", + e.publishEntryVariants(new JSONObject()).request().header(Util.BRANCH)); + Assertions.assertEquals( + "feature-branch", + e.unpublishEntryVariants(new JSONObject()).request().header(Util.BRANCH)); + } + + @Test + void fetchEntryVariant_branchArgumentOverridesStackForThatCallOnly() { + Contentstack cms = new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build(); + Stack stack = cms.stack(TestClient.API_KEY, TestClient.MANAGEMENT_TOKEN, "stack-branch"); + Entry e = stack.contentType("blog").entry("entryUid123"); + e.clearParams(); + + Request overridden = e.fetchEntryVariant("v1", "call-branch").request(); + Assertions.assertEquals("call-branch", overridden.header(Util.BRANCH)); + + Request defaultBranch = e.fetchEntryVariant("v1").request(); + Assertions.assertEquals("stack-branch", defaultBranch.header(Util.BRANCH)); + + Assertions.assertEquals("stack-branch", e.fetchEntryVariant("v1", null).request().header(Util.BRANCH)); + } + + @Test + void fetchEntryVariant_branchArgumentOverridesEntryLevelBranch() { + Contentstack cms = new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build(); + Stack stack = cms.stack(TestClient.API_KEY, TestClient.MANAGEMENT_TOKEN, "stack-branch"); + Entry e = stack.contentType("blog").entry("entryUid123").addBranch("entry-branch"); + e.clearParams(); + + Assertions.assertEquals( + "per-call-branch", + e.fetchEntryVariant("v1", "per-call-branch").request().header(Util.BRANCH)); + Assertions.assertEquals( + "entry-branch", + e.fetchEntryVariant("v1").request().header(Util.BRANCH)); + } + + @Test + void variantOperations_entryAddBranchOverridesStackBranch() { + Contentstack cms = new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build(); + Stack stack = cms.stack(TestClient.API_KEY, TestClient.MANAGEMENT_TOKEN, "stack-branch"); + Entry e = stack.contentType("blog").entry("entryUid123").addBranch("override-branch"); + e.clearParams(); + JSONObject body = Utils.readJson(CREATE_VARIANT_BODY); + Assertions.assertNotNull(body, "Missing " + CREATE_VARIANT_BODY); + + Request req = e.createEntryVariant("variantUid456", body).request(); + Assertions.assertEquals("override-branch", req.header(Util.BRANCH)); + } + + @Test + void deleteEntryVariant_forwardsStackBranchHeader() { + Contentstack cms = new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build(); + Stack stack = cms.stack(TestClient.API_KEY, TestClient.MANAGEMENT_TOKEN, "branch-for-delete"); + Entry e = stack.contentType("blog").entry("entryUid123"); + e.clearParams(); + Assertions.assertEquals( + "branch-for-delete", + e.deleteEntryVariant("toRemove").request().header(Util.BRANCH)); + } + + @Test + void updateEntryVariant_forwardsStackBranchHeader() { + Contentstack cms = new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build(); + Stack stack = cms.stack(TestClient.API_KEY, TestClient.MANAGEMENT_TOKEN, "branch-for-update"); + Entry e = stack.contentType("blog").entry("entryUid123"); + e.clearParams(); + JSONObject body = Utils.readJson(UPDATE_VARIANT_BODY); + Assertions.assertNotNull(body, "Missing " + UPDATE_VARIANT_BODY); + Assertions.assertEquals( + "branch-for-update", + e.updateEntryVariant("uid", body).request().header(Util.BRANCH)); + } + + @Test + void addHeaderBranch_matchesAddBranchForVariantRequests() { + Contentstack cms = new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build(); + Stack stack = cms.stack(TestClient.API_KEY, TestClient.MANAGEMENT_TOKEN); + Entry viaAddHeader = stack.contentType("blog").entry("e1").addHeader(Util.BRANCH, "hdr-branch"); + Entry viaAddBranch = stack.contentType("blog").entry("e2").addBranch("hdr-branch"); + viaAddHeader.clearParams(); + viaAddBranch.clearParams(); + JSONObject body = Utils.readJson(CREATE_VARIANT_BODY); + Assertions.assertNotNull(body, "Missing " + CREATE_VARIANT_BODY); + Assertions.assertEquals( + viaAddBranch.createEntryVariant("v", body).request().header(Util.BRANCH), + viaAddHeader.createEntryVariant("v", body).request().header(Util.BRANCH)); + } + + @Test + void fetchEntryVariant_blankSecondArgUsesStackBranchLikeNull() { + Contentstack cms = new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build(); + Stack stack = cms.stack(TestClient.API_KEY, TestClient.MANAGEMENT_TOKEN, "stack-branch"); + Entry e = stack.contentType("blog").entry("entryUid123"); + e.clearParams(); + Assertions.assertEquals( + "stack-branch", + e.fetchEntryVariant("v1", "").request().header(Util.BRANCH)); + } + + @Test + void variantOperations_noStackBranch_omitsBranchHeaderWhenNotSetOnEntry() { + Contentstack cms = new Contentstack.Builder().setAuthtoken(TestClient.AUTHTOKEN).build(); + Stack stack = cms.stack(TestClient.API_KEY, TestClient.MANAGEMENT_TOKEN); + Entry e = stack.contentType("blog").entry("entryUid123"); + e.clearParams(); + Assertions.assertNull(e.fetchEntryVariant("v1").request().header(Util.BRANCH)); + } +} diff --git a/src/test/resources/entry_variant/create_entry_variant.json b/src/test/resources/entry_variant/create_entry_variant.json new file mode 100644 index 00000000..f8745dad --- /dev/null +++ b/src/test/resources/entry_variant/create_entry_variant.json @@ -0,0 +1,15 @@ +{ + "entry": { + "title": "Blue|v1", + "url": "/blue", + "single_line": "blue variant v1", + "group": [ + { + "single_line": "blue variant group 1" + }, + { + "single_line": "blue variant group 2" + } + ] + } +} diff --git a/src/test/resources/entry_variant/publish_entry_variant.json b/src/test/resources/entry_variant/publish_entry_variant.json new file mode 100644 index 00000000..d3680a24 --- /dev/null +++ b/src/test/resources/entry_variant/publish_entry_variant.json @@ -0,0 +1,16 @@ +{ + "entry": { + "environments": ["development"], + "locales": ["en-us"], + "variants": [ + { + "version": 1 + } + ], + "variant_rules": { + "publish_latest_base": false, + "publish_latest_base_conditionally": true + } + }, + "locale": "en-us" +} diff --git a/src/test/resources/entry_variant/unpublish_entry_variant.json b/src/test/resources/entry_variant/unpublish_entry_variant.json new file mode 100644 index 00000000..daa27068 --- /dev/null +++ b/src/test/resources/entry_variant/unpublish_entry_variant.json @@ -0,0 +1,12 @@ +{ + "entry": { + "environments": ["development"], + "locales": ["en-us"], + "variants": [ + { + "version": 1 + } + ] + }, + "locale": "en-us" +} diff --git a/src/test/resources/entry_variant/update_entry_variant.json b/src/test/resources/entry_variant/update_entry_variant.json new file mode 100644 index 00000000..412cea49 --- /dev/null +++ b/src/test/resources/entry_variant/update_entry_variant.json @@ -0,0 +1,17 @@ +{ + "entry": { + "title": "blue |v2", + "url": "/blue", + "single_line": "blue variant v2 ", + "group": [ + { + "single_line": "Variant 2", + "multi_line": "Variant 2 Multi" + }, + { + "single_line": "Variant 1", + "multi_line": "Variant 1 Multi" + } + ] + } +}