diff --git a/src/main/java/io/supertokens/bulkimport/BulkImport.java b/src/main/java/io/supertokens/bulkimport/BulkImport.java new file mode 100644 index 000000000..4b8f55509 --- /dev/null +++ b/src/main/java/io/supertokens/bulkimport/BulkImport.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.bulkimport; + +import io.supertokens.pluginInterface.bulkimport.BulkImportUser; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.utils.Utils; + +import com.google.gson.JsonObject; + +import java.util.ArrayList; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BulkImport { + + public static final int GET_USERS_PAGINATION_LIMIT = 500; + public static final int GET_USERS_DEFAULT_LIMIT = 100; + + public static void addUsers(AppIdentifierWithStorage appIdentifierWithStorage, ArrayList users) + throws StorageQueryException, TenantOrAppNotFoundException { + while (true) { + try { + appIdentifierWithStorage.getBulkImportStorage().addBulkImportUsers(appIdentifierWithStorage, users); + break; + } catch (io.supertokens.pluginInterface.bulkimport.exceptions.DuplicateUserIdException ignored) { + // We re-generate the user id for every user and retry + for (BulkImportUser user : users) { + user.id = Utils.getUUID(); + } + } + } + } + + public static BulkImportUserPaginationContainer getUsers(AppIdentifierWithStorage appIdentifierWithStorage, + @Nonnull Integer limit, @Nullable String status, @Nullable String paginationToken) + throws StorageQueryException, BulkImportUserPaginationToken.InvalidTokenException, + TenantOrAppNotFoundException { + JsonObject[] users; + + if (paginationToken == null) { + users = appIdentifierWithStorage.getBulkImportStorage() + .getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, null); + } else { + BulkImportUserPaginationToken tokenInfo = BulkImportUserPaginationToken.extractTokenInfo(paginationToken); + users = appIdentifierWithStorage.getBulkImportStorage() + .getBulkImportUsers(appIdentifierWithStorage, limit + 1, status, tokenInfo.bulkImportUserId); + } + + String nextPaginationToken = null; + int maxLoop = users.length; + if (users.length == limit + 1) { + maxLoop = limit; + nextPaginationToken = new BulkImportUserPaginationToken(users[limit].get("id").getAsString()) + .generateToken(); + } + + JsonObject[] resultUsers = new JsonObject[maxLoop]; + System.arraycopy(users, 0, resultUsers, 0, maxLoop); + return new BulkImportUserPaginationContainer(resultUsers, nextPaginationToken); + } +} diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java new file mode 100644 index 000000000..a9e4d644c --- /dev/null +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationContainer.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.bulkimport; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import com.google.gson.JsonObject; + +public class BulkImportUserPaginationContainer { + public final JsonObject[] users; + public final String nextPaginationToken; + + public BulkImportUserPaginationContainer(@Nonnull JsonObject[] users, @Nullable String nextPaginationToken) { + this.users = users; + this.nextPaginationToken = nextPaginationToken; + } +} \ No newline at end of file diff --git a/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java new file mode 100644 index 000000000..d66d02041 --- /dev/null +++ b/src/main/java/io/supertokens/bulkimport/BulkImportUserPaginationToken.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.bulkimport; + +import java.util.Base64; + +public class BulkImportUserPaginationToken { + public final String bulkImportUserId; + + public BulkImportUserPaginationToken(String bulkImportUserId) { + this.bulkImportUserId = bulkImportUserId; + } + + public static BulkImportUserPaginationToken extractTokenInfo(String token) throws InvalidTokenException { + try { + String bulkImportUserId = new String(Base64.getDecoder().decode(token)); + return new BulkImportUserPaginationToken(bulkImportUserId); + } catch (Exception e) { + throw new InvalidTokenException(); + } + } + + public String generateToken() { + return new String(Base64.getEncoder().encode((this.bulkImportUserId).getBytes())); + } + + public static class InvalidTokenException extends Exception { + + private static final long serialVersionUID = 6289026174830695478L; + } +} diff --git a/src/main/java/io/supertokens/webserver/Webserver.java b/src/main/java/io/supertokens/webserver/Webserver.java index 1664fffe2..6f9c30ef1 100644 --- a/src/main/java/io/supertokens/webserver/Webserver.java +++ b/src/main/java/io/supertokens/webserver/Webserver.java @@ -28,6 +28,7 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.api.accountlinking.*; import io.supertokens.webserver.api.bulkimport.AddBulkImportUsers; +import io.supertokens.webserver.api.bulkimport.GetBulkImportUsers; import io.supertokens.webserver.api.core.*; import io.supertokens.webserver.api.dashboard.*; import io.supertokens.webserver.api.emailpassword.UserAPI; @@ -261,6 +262,7 @@ private void setupRoutes() { addAPI(new RequestStatsAPI(main)); addAPI(new AddBulkImportUsers(main)); + addAPI(new GetBulkImportUsers(main)); StandardContext context = tomcatReference.getContext(); Tomcat tomcat = tomcatReference.getTomcat(); diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java b/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java index 0f98470a1..0e5d3df46 100644 --- a/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/AddBulkImportUsers.java @@ -17,13 +17,18 @@ package io.supertokens.webserver.api.bulkimport; import io.supertokens.Main; +import io.supertokens.bulkimport.BulkImport; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlag; import io.supertokens.multitenancy.Multitenancy; -import io.supertokens.pluginInterface.bulkimport.BulkImportStorage; import io.supertokens.pluginInterface.bulkimport.BulkImportUser; import io.supertokens.pluginInterface.bulkimport.exceptions.InvalidBulkImportDataException; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; import io.supertokens.pluginInterface.multitenancy.AppIdentifier; import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; import io.supertokens.pluginInterface.multitenancy.TenantConfig; +import io.supertokens.pluginInterface.multitenancy.TenantIdentifier; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; import io.supertokens.webserver.InputParser; import io.supertokens.webserver.WebserverAPI; import jakarta.servlet.ServletException; @@ -55,31 +60,27 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S JsonObject input = InputParser.parseJsonObjectOrThrowError(req); JsonArray users = InputParser.parseArrayOrThrowError(input, "users", false); - if (users.size() > MAX_USERS_TO_ADD) { + if (users.size() <= 0 || users.size() > MAX_USERS_TO_ADD) { JsonObject errorResponseJson = new JsonObject(); - errorResponseJson.addProperty("error", "You can only add 1000 users at a time."); - super.sendJsonResponse(400, errorResponseJson, resp); - return; + String errorMsg = users.size() <= 0 ? "You need to add at least one user." + : "You can only add 10000 users at a time."; + errorResponseJson.addProperty("error", errorMsg); + throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); } - AppIdentifier appIdentifier = null; - try { - appIdentifier = getTenantIdentifierFromRequest(req).toAppIdentifier(); - } catch (ServletException e) { - throw new ServletException(e); - } - - TenantConfig[] allTenantConfigs = Multitenancy.getAllTenantsForApp(appIdentifier, main); - ArrayList validTenantIds = new ArrayList<>(); - Arrays.stream(allTenantConfigs) - .forEach(tenantConfig -> validTenantIds.add(tenantConfig.tenantIdentifier.getTenantId())); + AppIdentifier appIdentifier = getTenantIdentifierFromRequest(req).toAppIdentifier(); JsonArray errorsJson = new JsonArray(); ArrayList usersToAdd = new ArrayList<>(); for (int i = 0; i < users.size(); i++) { try { - usersToAdd.add(new BulkImportUser(users.get(i).getAsJsonObject(), validTenantIds, null)); + BulkImportUser user = new BulkImportUser(users.get(i).getAsJsonObject(), null); + usersToAdd.add(user); + + for (BulkImportUser.LoginMethod loginMethod : user.loginMethods) { + validateTenantId(appIdentifier, loginMethod.tenantId, loginMethod.recipeId); + } } catch (InvalidBulkImportDataException e) { JsonObject errorObj = new JsonObject(); @@ -89,12 +90,6 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S errorObj.addProperty("index", i); errorObj.add("errors", errors); - - errorsJson.add(errorObj); - } catch (Exception e) { - JsonObject errorObj = new JsonObject(); - errorObj.addProperty("index", i); - errorObj.addProperty("errors", "An unknown error occurred"); errorsJson.add(errorObj); } } @@ -104,21 +99,46 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S errorResponseJson.addProperty("error", "Data has missing or invalid fields. Please check the users field for more details."); errorResponseJson.add("users", errorsJson); - super.sendJsonResponse(400, errorResponseJson, resp); - return; + throw new ServletException(new WebserverAPI.BadRequestException(errorResponseJson.toString())); } try { AppIdentifierWithStorage appIdentifierWithStorage = getAppIdentifierWithStorage(req); - BulkImportStorage storage = appIdentifierWithStorage.getBulkImportStorage(); - storage.addBulkImportUsers(appIdentifierWithStorage, usersToAdd); - } catch (Exception e) { + BulkImport.addUsers(appIdentifierWithStorage, usersToAdd); + } catch (TenantOrAppNotFoundException | StorageQueryException e) { throw new ServletException(e); } JsonObject result = new JsonObject(); result.addProperty("status", "OK"); super.sendJsonResponse(200, result, resp); + } + + private void validateTenantId(AppIdentifier appIdentifier, String tenantId, String recipeId) + throws InvalidBulkImportDataException, ServletException { + if (tenantId == null || tenantId.equals(TenantIdentifier.DEFAULT_TENANT_ID)) { + return; + } + + try { + if (Arrays.stream(FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures()) + .noneMatch(t -> t == EE_FEATURES.MULTI_TENANCY)) { + throw new InvalidBulkImportDataException(new ArrayList<>( + Arrays.asList("Multitenancy must be enabled before importing users to a different tenant."))); + } + } catch (TenantOrAppNotFoundException | StorageQueryException e) { + throw new ServletException(e); + } + TenantConfig[] allTenantConfigs = Multitenancy + .getAllTenantsForApp(appIdentifier, main); + ArrayList validTenantIds = new ArrayList<>(); + Arrays.stream(allTenantConfigs) + .forEach(tenantConfig -> validTenantIds.add(tenantConfig.tenantIdentifier.getTenantId())); + + if (!validTenantIds.contains(tenantId)) { + throw new InvalidBulkImportDataException( + new ArrayList<>(Arrays.asList("Invalid tenantId: " + tenantId + " for " + recipeId + " recipe."))); + } } } diff --git a/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java b/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java new file mode 100644 index 000000000..f349ac55b --- /dev/null +++ b/src/main/java/io/supertokens/webserver/api/bulkimport/GetBulkImportUsers.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.webserver.api.bulkimport; + +import java.io.IOException; +import java.util.Arrays; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import io.supertokens.Main; +import io.supertokens.bulkimport.BulkImport; +import io.supertokens.bulkimport.BulkImportUserPaginationContainer; +import io.supertokens.bulkimport.BulkImportUserPaginationToken; +import io.supertokens.output.Logging; +import io.supertokens.pluginInterface.exceptions.StorageQueryException; +import io.supertokens.pluginInterface.multitenancy.AppIdentifierWithStorage; +import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException; +import io.supertokens.utils.Utils; +import io.supertokens.webserver.InputParser; +import io.supertokens.webserver.WebserverAPI; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class GetBulkImportUsers extends WebserverAPI { + public GetBulkImportUsers(Main main) { + super(main, ""); + } + + @Override + public String getPath() { + return "/bulk-import/users"; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String status = InputParser.getQueryParamOrThrowError(req, "status", true); + String paginationToken = InputParser.getQueryParamOrThrowError(req, "paginationToken", true); + Integer limit = InputParser.getIntQueryParamOrThrowError(req, "limit", true); + + if (limit != null) { + if (limit > BulkImport.GET_USERS_PAGINATION_LIMIT) { + throw new ServletException( + new BadRequestException("Max limit allowed is " + BulkImport.GET_USERS_PAGINATION_LIMIT)); + } else if (limit < 1) { + throw new ServletException(new BadRequestException("limit must a positive integer with min value 1")); + } + } else { + limit = BulkImport.GET_USERS_DEFAULT_LIMIT; + } + + if (status != null + && !Arrays.asList("NEW", "PROCESSING", "FAILED").contains(status)) { + throw new ServletException(new BadRequestException( + "Invalid value for status. Pass one of NEW, PROCESSING or, FAILED!")); + } + + AppIdentifierWithStorage appIdentifierWithStorage = null; + + try { + appIdentifierWithStorage = this.getAppIdentifierWithStorage(req); + } catch (TenantOrAppNotFoundException e) { + throw new ServletException(e); + } + + try { + BulkImportUserPaginationContainer users = BulkImport.getUsers(appIdentifierWithStorage, limit, status, + paginationToken); + JsonObject result = new JsonObject(); + result.addProperty("status", "OK"); + + JsonArray usersJson = new JsonArray(); + for (JsonObject user : users.users) { + usersJson.add(user); + } + result.add("users", usersJson); + + if (users.nextPaginationToken != null) { + result.addProperty("nextPaginationToken", users.nextPaginationToken); + } + super.sendJsonResponse(200, result, resp); + } catch (BulkImportUserPaginationToken.InvalidTokenException e) { + Logging.debug(main, null, Utils.exceptionStacktraceToString(e)); + throw new ServletException(new BadRequestException("invalid pagination token")); + } catch (StorageQueryException | TenantOrAppNotFoundException e) { + throw new ServletException(e); + } + } +} diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java index abc5d1aa0..06fcd7096 100644 --- a/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java +++ b/src/test/java/io/supertokens/test/bulkimport/apis/AddBulkImportUsersTest.java @@ -33,6 +33,8 @@ import com.google.gson.JsonParser; import io.supertokens.ProcessState; +import io.supertokens.featureflag.EE_FEATURES; +import io.supertokens.featureflag.FeatureFlagTestContent; import io.supertokens.pluginInterface.STORAGE_TYPE; import io.supertokens.storageLayer.StorageLayer; import io.supertokens.test.TestingProcessManager; @@ -285,6 +287,40 @@ public void shouldThrow400Error() throws Exception { "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"email should be of type string for a passwordless recipe.\",\"phoneNumber should be of type string for a passwordless recipe.\"]}]}"); } } + // Validate tenantId + { + // CASE 1: Different tenantId when multitenancy is not enabled + try { + JsonObject request = new JsonParser().parse( + "{\"users\":[{\"loginMethods\":[{\"tenantId\":\"invalid\",\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"Multitenancy must be enabled before importing users to a different tenant.\"]}]}"); + } + // CASE 2: Different tenantId when multitenancy is enabled + try { + FeatureFlagTestContent.getInstance(process.getProcess()) + .setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MULTI_TENANCY}); + + JsonObject request = new JsonParser().parse( + "{\"users\":[{\"loginMethods\":[{\"tenantId\":\"invalid\",\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}") + .getAsJsonObject(); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, + "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"Invalid tenantId: invalid for passwordless recipe.\"]}]}"); + } + } // No two loginMethods can have isPrimary as true { // CASE 1: email, passwordHash and hashingAlgorithm are not present @@ -301,7 +337,20 @@ public void shouldThrow400Error() throws Exception { "{\"error\":\"" + genericErrMsg + "\",\"users\":[{\"index\":0,\"errors\":[\"No two loginMethods can have isPrimary as true.\"]}]}"); } } - // Can't import than 10000 users at a time + // Can't import less than 1 user at a time + { + try { + JsonObject request = generateUsersJson(0); + HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + String responseString = getResponseMessageFromError(e.getMessage()); + assertEquals(400, e.statusCode); + assertEquals(responseString, "{\"error\":\"You need to add at least one user.\"}"); + } + } + // Can't import more than 10000 users at a time { try { JsonObject request = generateUsersJson(10001); @@ -311,7 +360,7 @@ public void shouldThrow400Error() throws Exception { } catch (io.supertokens.test.httpRequest.HttpResponseException e) { String responseString = getResponseMessageFromError(e.getMessage()); assertEquals(400, e.statusCode); - assertEquals(responseString, "{\"error\":\"You can only add 1000 users at a time.\"}"); + assertEquals(responseString, "{\"error\":\"You can only add 10000 users at a time.\"}"); } } @@ -383,7 +432,7 @@ private static JsonObject createEmailLoginMethod(String email) { private static JsonObject createThirdPartyLoginMethod(String email) { JsonObject loginMethod = new JsonObject(); - loginMethod.addProperty("tenantId", "somethingelse"); + loginMethod.addProperty("tenantId", "public"); loginMethod.addProperty("recipeId", "thirdparty"); loginMethod.addProperty("email", email); loginMethod.addProperty("thirdPartyId", "google"); diff --git a/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java new file mode 100644 index 000000000..7dbba3b04 --- /dev/null +++ b/src/test/java/io/supertokens/test/bulkimport/apis/GetBulkImportUsersTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.supertokens.test.bulkimport.apis; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import io.supertokens.ProcessState; +import io.supertokens.pluginInterface.STORAGE_TYPE; +import io.supertokens.storageLayer.StorageLayer; +import io.supertokens.test.TestingProcessManager; +import io.supertokens.test.Utils; +import io.supertokens.test.httpRequest.HttpRequestForTesting; + +public class GetBulkImportUsersTest { + @Rule + public TestRule watchman = Utils.getOnFailure(); + + @AfterClass + public static void afterTesting() { + Utils.afterTesting(); + } + + @Before + public void beforeEach() { + Utils.reset(); + } + + @Test + public void shouldReturn400Error() throws Exception { + String[] args = { "../" }; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + try { + Map params = new HashMap<>(); + params.put("status", "INVALID_STATUS"); + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals( + "Http error. Status Code: 400. Message: Invalid value for status. Pass one of NEW, PROCESSING or, FAILED!", + e.getMessage()); + } + + try { + Map params = new HashMap<>(); + params.put("limit", "0"); + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: limit must a positive integer with min value 1", + e.getMessage()); + } + + try { + Map params = new HashMap<>(); + params.put("limit", "501"); + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: Max limit allowed is 500", e.getMessage()); + } + + try { + Map params = new HashMap<>(); + params.put("paginationToken", "invalid_token"); + HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + + } catch (io.supertokens.test.httpRequest.HttpResponseException e) { + assertEquals(400, e.statusCode); + assertEquals("Http error. Status Code: 400. Message: invalid pagination token", e.getMessage()); + } + + process.kill(); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } + + @Test + public void shouldReturn200Response() throws Exception { + String[] args = { "../" }; + + TestingProcessManager.TestingProcess process = TestingProcessManager.start(args); + assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED)); + + if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) { + return; + } + + // Create a bulk import user to test the GET API + String rawData = "{\"users\":[{\"loginMethods\":[{\"recipeId\":\"passwordless\",\"email\":\"johndoe@gmail.com\"}]}]}"; + { + JsonObject request = new JsonParser().parse(rawData).getAsJsonObject(); + JsonObject res = HttpRequestForTesting.sendJsonPOSTRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/add-users", + request, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + assert res.get("status").getAsString().equals("OK"); + } + + Map params = new HashMap<>(); + JsonObject response = HttpRequestForTesting.sendGETRequest(process.getProcess(), "", + "http://localhost:3567/bulk-import/users", + params, 1000, 1000, null, Utils.getCdiVersionStringLatestForTests(), null); + assertEquals("OK", response.get("status").getAsString()); + JsonArray bulkImportUsers = response.get("users").getAsJsonArray(); + assertEquals(1, bulkImportUsers.size()); + JsonObject bulkImportUserJson = bulkImportUsers.get(0).getAsJsonObject(); + bulkImportUserJson.get("status").getAsString().equals("NEW"); + bulkImportUserJson.get("raw_data").getAsString().equals(rawData); + + process.kill(); + Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED)); + } +}