|
| 1 | +package com.contentstack.cms.stack; |
| 2 | + |
| 3 | +import com.contentstack.cms.TestClient; |
| 4 | + |
| 5 | +import okhttp3.ResponseBody; |
| 6 | +import org.json.simple.JSONObject; |
| 7 | +import org.json.simple.parser.JSONParser; |
| 8 | +import org.junit.jupiter.api.*; |
| 9 | +import retrofit2.Response; |
| 10 | + |
| 11 | +import java.io.IOException; |
| 12 | +import java.util.ArrayList; |
| 13 | +import java.util.Arrays; |
| 14 | +import java.util.Collections; |
| 15 | +import java.util.HashSet; |
| 16 | +import java.util.List; |
| 17 | + |
| 18 | +import static org.junit.jupiter.api.Assertions.*; |
| 19 | + |
| 20 | +/** |
| 21 | + * Live checks for {@code publish_config.environments} on branch create (DX-31564). |
| 22 | + * <p> |
| 23 | + * Needs a real stack in {@code .env}; without credentials the whole class is skipped. |
| 24 | + * The environment names {@value #ENV_DEVELOPMENT} and {@value #ENV_PRODUCTION} must exist |
| 25 | + * on the test stack. The source branch {@value #SOURCE} must exist too. |
| 26 | + */ |
| 27 | +@Tag("api") |
| 28 | +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) |
| 29 | +public class BranchAPITest { |
| 30 | + |
| 31 | + static final String ENV_DEVELOPMENT = "development"; |
| 32 | + static final String ENV_PRODUCTION = "production"; |
| 33 | + static final String UNKNOWN_ENV = "no_such_env_dx31564"; |
| 34 | + static final String SOURCE = "main"; |
| 35 | + |
| 36 | + /** |
| 37 | + * Unique per run, so reruns on the same stack do not collide. Branch UIDs allow at most |
| 38 | + * 15 characters, lowercase letters and digits only: "b" plus the last 5 base-36 digits of |
| 39 | + * the current time, then one letter per UID below (7 characters in total). |
| 40 | + */ |
| 41 | + private static final String RUN = runId(); |
| 42 | + private static final String UID_TWO = RUN + "a"; |
| 43 | + private static final String UID_EMPTY = RUN + "b"; |
| 44 | + private static final String UID_UNKNOWN = RUN + "c"; |
| 45 | + private static final String UID_MIXED = RUN + "d"; |
| 46 | + |
| 47 | + private static String runId() { |
| 48 | + String time = Long.toString(System.currentTimeMillis(), 36); |
| 49 | + return "b" + time.substring(time.length() - 5); |
| 50 | + } |
| 51 | + |
| 52 | + /** Branches this class created, deleted in {@link #cleanUp()}. */ |
| 53 | + private static final List<String> CREATED = new ArrayList<>(); |
| 54 | + |
| 55 | + private static Stack stack; |
| 56 | + |
| 57 | + @BeforeAll |
| 58 | + static void setup() { |
| 59 | + try { |
| 60 | + stack = TestClient.getStack(); |
| 61 | + } catch (ExceptionInInitializerError | NoClassDefFoundError e) { |
| 62 | + // TestClient loads .env in its static initializer; without it the class cannot load. |
| 63 | + Assumptions.assumeTrue(false, "Skipping: TestClient could not load .env"); |
| 64 | + } |
| 65 | + Assumptions.assumeFalse(TestClient.isUsingDefaultStackCredentials(), |
| 66 | + "Skipping: stack credentials not set in .env"); |
| 67 | + } |
| 68 | + |
| 69 | + @AfterAll |
| 70 | + static void cleanUp() { |
| 71 | + for (String uid : CREATED) { |
| 72 | + try { |
| 73 | + stack.branch(uid).delete().execute(); |
| 74 | + } catch (IOException e) { |
| 75 | + System.out.println("Cleanup failed for branch " + uid + ": " + e.getLocalizedMessage()); |
| 76 | + } |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + @Test |
| 81 | + @Order(1) |
| 82 | + void createWithTwoEnvironmentNames() throws IOException { |
| 83 | + Response<ResponseBody> response = stack.branch() |
| 84 | + .create(body(UID_TWO, Arrays.asList(ENV_DEVELOPMENT, ENV_PRODUCTION))).execute(); |
| 85 | + CREATED.add(UID_TWO); |
| 86 | + assertTrue(response.isSuccessful(), "create with two environment names should succeed"); |
| 87 | + } |
| 88 | + |
| 89 | + @Test |
| 90 | + @Order(2) |
| 91 | + void fetchBranchCarriesTwoEnvironmentNames() throws Exception { |
| 92 | + Response<ResponseBody> response = stack.branch(UID_TWO).fetch().execute(); |
| 93 | + assertTrue(response.isSuccessful(), "fetch of the created branch should succeed"); |
| 94 | + assertEquals(new HashSet<>(Arrays.asList(ENV_DEVELOPMENT, ENV_PRODUCTION)), |
| 95 | + new HashSet<>(environmentsOf(response))); |
| 96 | + } |
| 97 | + |
| 98 | + @Test |
| 99 | + @Order(3) |
| 100 | + void createWithEmptyListClonesNoEnvironments() throws Exception { |
| 101 | + Response<ResponseBody> response = stack.branch() |
| 102 | + .create(body(UID_EMPTY, Collections.<String>emptyList())).execute(); |
| 103 | + CREATED.add(UID_EMPTY); |
| 104 | + assertTrue(response.isSuccessful(), "create with an empty list should succeed"); |
| 105 | + |
| 106 | + Response<ResponseBody> fetched = stack.branch(UID_EMPTY).fetch().execute(); |
| 107 | + assertTrue(fetched.isSuccessful(), "fetch of the created branch should succeed"); |
| 108 | + assertTrue(environmentsOf(fetched).isEmpty(), "an empty list must come back empty"); |
| 109 | + } |
| 110 | + |
| 111 | + @Test |
| 112 | + @Order(4) |
| 113 | + void createWithUnknownNameIsRejectedAndCreatesNoBranch() throws Exception { |
| 114 | + Response<ResponseBody> response = stack.branch() |
| 115 | + .create(body(UID_UNKNOWN, Collections.singletonList(UNKNOWN_ENV))).execute(); |
| 116 | + CREATED.add(UID_UNKNOWN); |
| 117 | + assertEquals(422, response.code(), "an unknown environment name should give the API's 422"); |
| 118 | + assertNotNull(response.errorBody(), "the API's error body should be present"); |
| 119 | + assertTrue(response.errorBody().string().contains("publish_config.environments"), |
| 120 | + "the 422 body should name publish_config.environments"); |
| 121 | + |
| 122 | + Response<ResponseBody> fetched = stack.branch(UID_UNKNOWN).fetch().execute(); |
| 123 | + assertFalse(fetched.isSuccessful(), "no branch should exist after a rejected create"); |
| 124 | + } |
| 125 | + |
| 126 | + @Test |
| 127 | + @Order(5) |
| 128 | + void createWithMixedNamesIsRejectedAndCreatesNoBranch() throws Exception { |
| 129 | + Response<ResponseBody> response = stack.branch() |
| 130 | + .create(body(UID_MIXED, Arrays.asList(ENV_DEVELOPMENT, UNKNOWN_ENV))).execute(); |
| 131 | + CREATED.add(UID_MIXED); |
| 132 | + assertEquals(422, response.code(), "a mix with an unknown name should give the API's 422"); |
| 133 | + assertNotNull(response.errorBody(), "the API's error body should be present"); |
| 134 | + assertTrue(response.errorBody().string().contains("publish_config.environments"), |
| 135 | + "the 422 body should name publish_config.environments"); |
| 136 | + |
| 137 | + Response<ResponseBody> fetched = stack.branch(UID_MIXED).fetch().execute(); |
| 138 | + assertFalse(fetched.isSuccessful(), "no branch should exist after a rejected create"); |
| 139 | + } |
| 140 | + |
| 141 | + /** Builds {@code {"branch": {uid, source, publish_config: {environments}}}}. */ |
| 142 | + private static JSONObject body(String uid, List<String> environments) { |
| 143 | + JSONObject publishConfig = new JSONObject(); |
| 144 | + publishConfig.put("environments", environments); |
| 145 | + JSONObject branch = new JSONObject(); |
| 146 | + branch.put("uid", uid); |
| 147 | + branch.put("source", SOURCE); |
| 148 | + branch.put("publish_config", publishConfig); |
| 149 | + JSONObject root = new JSONObject(); |
| 150 | + root.put("branch", branch); |
| 151 | + return root; |
| 152 | + } |
| 153 | + |
| 154 | + /** Reads {@code branch.publish_config.environments} from a fetch response. */ |
| 155 | + private static List<String> environmentsOf(Response<ResponseBody> response) throws Exception { |
| 156 | + JSONObject json = (JSONObject) new JSONParser().parse(response.body().string()); |
| 157 | + JSONObject branch = (JSONObject) json.get("branch"); |
| 158 | + assertNotNull(branch, "the fetch response should carry a 'branch' object"); |
| 159 | + JSONObject publishConfig = (JSONObject) branch.get("publish_config"); |
| 160 | + assertNotNull(publishConfig, "the branch should carry a 'publish_config' object"); |
| 161 | + Object environments = publishConfig.get("environments"); |
| 162 | + assertNotNull(environments, "publish_config should carry an 'environments' list"); |
| 163 | + List<String> names = new ArrayList<>(); |
| 164 | + for (Object name : (List<?>) environments) { |
| 165 | + names.add((String) name); |
| 166 | + } |
| 167 | + return names; |
| 168 | + } |
| 169 | +} |
0 commit comments