diff --git a/build.gradle b/build.gradle index 96605fc8..e69f49a5 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = "com.marklogic" -version = "4.5.3" +version = "4.6.0" java { sourceCompatibility = 1.8 @@ -16,6 +16,7 @@ java { } repositories { + mavenLocal() mavenCentral() maven { url "https://nexus.marklogic.com/repository/maven-snapshots/" @@ -23,13 +24,17 @@ repositories { } dependencies { - api 'com.marklogic:ml-javaclient-util:4.5.1' - api 'org.springframework:spring-web:5.3.27' - api 'com.fasterxml.jackson.core:jackson-databind:2.14.1' + api 'com.marklogic:ml-javaclient-util:4.6.0' + api 'org.springframework:spring-web:5.3.29' + api 'com.fasterxml.jackson.core:jackson-databind:2.15.2' implementation 'jaxen:jaxen:1.2.0' - implementation 'com.squareup.okhttp3:okhttp:4.10.0' + + // Forcing usage of 3.4.0 instead of 3.2.0 to address vulnerability - https://security.snyk.io/vuln/SNYK-JAVA-COMSQUAREUPOKIO-5820002 + implementation 'com.squareup.okio:okio:3.4.0' + implementation 'com.squareup.okhttp3:okhttp:4.11.0' implementation 'io.github.rburgst:okhttp-digest:2.7' + implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation 'org.jdom:jdom2:2.0.6.1' @@ -54,8 +59,8 @@ dependencies { compileOnly "com.beust:jcommander:1.82" compileOnly "ch.qos.logback:logback-classic:1.3.5" - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3' - testImplementation 'org.springframework:spring-test:5.3.27' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' + testImplementation 'org.springframework:spring-test:5.3.29' testImplementation 'commons-io:commons-io:2.11.0' testImplementation 'xmlunit:xmlunit:1.6' diff --git a/pom.xml b/pom.xml index 1dcb1020..22830b64 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ It is not intended to be used to build this project. 4.0.0 com.marklogic ml-app-deployer - 4.5.3 + 4.6.0 com.marklogic:ml-app-deployer Java client for the MarkLogic REST Management API and for deploying applications to MarkLogic https://github.com/marklogic/ml-app-deployer @@ -40,19 +40,19 @@ It is not intended to be used to build this project. com.marklogic ml-javaclient-util - 4.5.1 + 4.6.0 compile org.springframework spring-web - 5.3.27 + 5.3.29 compile com.fasterxml.jackson.core jackson-databind - 2.14.1 + 2.15.2 compile diff --git a/src/main/java/com/marklogic/appdeployer/AppConfig.java b/src/main/java/com/marklogic/appdeployer/AppConfig.java index 8ea88ab4..fef56566 100644 --- a/src/main/java/com/marklogic/appdeployer/AppConfig.java +++ b/src/main/java/com/marklogic/appdeployer/AppConfig.java @@ -155,6 +155,11 @@ public class AppConfig { private String cpfDatabaseName; private String schemasDatabaseName; + // Since 4.6.0; affects loading of modules, schemas, and data. + // Disabled by default for backwards compatibility; will likely default to true in 5.0.0 release. + private boolean cascadeCollections; + private boolean cascadePermissions; + private List modulePaths; private boolean staticCheckAssets = false; private boolean staticCheckLibraryAssets = false; @@ -1535,4 +1540,20 @@ public String getAppServicesSamlToken() { public void setAppServicesSamlToken(String appServicesSamlToken) { this.appServicesSamlToken = appServicesSamlToken; } + + public boolean isCascadeCollections() { + return cascadeCollections; + } + + public void setCascadeCollections(boolean cascadeCollections) { + this.cascadeCollections = cascadeCollections; + } + + public boolean isCascadePermissions() { + return cascadePermissions; + } + + public void setCascadePermissions(boolean cascadePermissions) { + this.cascadePermissions = cascadePermissions; + } } diff --git a/src/main/java/com/marklogic/appdeployer/DefaultAppConfigFactory.java b/src/main/java/com/marklogic/appdeployer/DefaultAppConfigFactory.java index 67a6196c..f0612338 100644 --- a/src/main/java/com/marklogic/appdeployer/DefaultAppConfigFactory.java +++ b/src/main/java/com/marklogic/appdeployer/DefaultAppConfigFactory.java @@ -791,6 +791,16 @@ public void initialize() { config.setModulesLoaderBatchSize(Integer.parseInt(prop)); }); + propertyConsumerMap.put("mlCascadeCollections", (config, prop) -> { + logger.info("Cascade collections.properties configuration when loading data, modules, and schemas: " + prop); + config.setCascadeCollections(Boolean.parseBoolean(prop)); + }); + + propertyConsumerMap.put("mlCascadePermissions", (config, prop) -> { + logger.info("Cascade permissions.properties configuration when loading data, modules, and schemas: " + prop); + config.setCascadePermissions(Boolean.parseBoolean(prop)); + }); + /** * The following properties are all for generating Entity Services artifacts. */ diff --git a/src/main/java/com/marklogic/appdeployer/command/CommandContext.java b/src/main/java/com/marklogic/appdeployer/command/CommandContext.java index 0934bea3..95b44dfa 100644 --- a/src/main/java/com/marklogic/appdeployer/command/CommandContext.java +++ b/src/main/java/com/marklogic/appdeployer/command/CommandContext.java @@ -120,6 +120,11 @@ public Map getContextMap() { return contextMap; } + /** + * @param contextMap + * @deprecated since 4.6.0, will be removed in 5.0.0; the contextMap is not intended to be replaced. + */ + @Deprecated public void setContextMap(Map contextMap) { this.contextMap = contextMap; } diff --git a/src/main/java/com/marklogic/appdeployer/command/TestConnectionsCommand.java b/src/main/java/com/marklogic/appdeployer/command/TestConnectionsCommand.java new file mode 100644 index 00000000..b8c297ba --- /dev/null +++ b/src/main/java/com/marklogic/appdeployer/command/TestConnectionsCommand.java @@ -0,0 +1,328 @@ +package com.marklogic.appdeployer.command; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.marklogic.appdeployer.AppConfig; +import com.marklogic.client.DatabaseClient; +import com.marklogic.client.ext.SecurityContextType; +import com.marklogic.mgmt.ManageClient; +import com.marklogic.mgmt.admin.AdminManager; +import com.marklogic.mgmt.cma.ConfigurationManager; +import com.marklogic.mgmt.resource.clusters.ClusterManager; +import com.marklogic.rest.util.RestConfig; +import org.springframework.web.client.DefaultResponseErrorHandler; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.ResponseErrorHandler; + +import javax.net.ssl.SSLContext; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * Command for testing each of the connections that can be made to MarkLogic based on the configuration in a + * {@code CommandContext}. + * + * @since 4.6.0 + */ +public class TestConnectionsCommand extends AbstractCommand { + + /** + * If run in a deployment process, this should run immediately so as to fail fast. + */ + public TestConnectionsCommand() { + setExecuteSortOrder(0); + } + + /** + * Can be included in a deployment process so that the deployment fails if any of the connections fail. + * + * @param context + */ + @Override + public void execute(CommandContext context) { + TestResults results = testConnections(context); + if (results.anyTestFailed()) { + throw new RuntimeException(results.toString()); + } + logger.info(results.toString()); + } + + /** + * Intended for execution outside a deployment process, where the client wants access to the test results and + * will choose how to present those to a user. + * + * @param context + * @return + */ + public TestResults testConnections(CommandContext context) { + try { + TestResult manageResult = testManageAppServer(context.getManageClient()); + TestResult adminResult = testAdminAppServer(context.getAdminManager()); + + TestResult appServicesResult = null; + TestResult restResult = null; + TestResult testRestResult = null; + if (manageResult.isSucceeded()) { + List serverPorts = getAppServerPorts(context.getManageClient()); + appServicesResult = testAppServicesAppServer(context.getAppConfig(), serverPorts); + restResult = testRestAppServer(context.getAppConfig(), serverPorts); + testRestResult = testTestRestAppServer(context.getAppConfig(), serverPorts); + } + + return new TestResults(manageResult, adminResult, appServicesResult, restResult, testRestResult); + } catch (Exception ex) { + // We don't expect any exceptions above, as each connection test has its own try/catch block. + // This is simply to pretty up the error a bit. + throw new RuntimeException("Unable to test connections; cause: " + ex.getMessage(), ex); + } + } + + private List getAppServerPorts(ManageClient manageClient) { + JsonNode json = new ConfigurationManager(manageClient).getResourcesAsJson("server").getBody(); + ArrayNode servers = (ArrayNode) json.get("config").get(0).get("server"); + List ports = new ArrayList<>(); + servers.forEach(server -> { + if (server.has("port")) { + ports.add(server.get("port").asInt()); + } + }); + return ports; + } + + private TestResult testAppServicesAppServer(AppConfig appConfig, List serverPorts) { + if (appConfig.getAppServicesPort() != null && serverPorts.contains(appConfig.getAppServicesPort())) { + return testWithDatabaseClient(appConfig.getHost(), appConfig.getAppServicesPort(), + appConfig.getAppServicesSslContext(), appConfig.getAppServicesSecurityContextType(), + appConfig.getAppServicesUsername(), () -> appConfig.newAppServicesDatabaseClient(null)); + } + return null; + } + + private TestResult testRestAppServer(AppConfig appConfig, List serverPorts) { + if (appConfig.getRestPort() != null && serverPorts.contains(appConfig.getRestPort())) { + return testWithDatabaseClient(appConfig.getHost(), appConfig.getRestPort(), + appConfig.getRestSslContext(), appConfig.getRestSecurityContextType(), + appConfig.getRestAdminUsername(), appConfig::newDatabaseClient); + } + return null; + } + + private TestResult testTestRestAppServer(AppConfig appConfig, List serverPorts) { + if (appConfig.getTestRestPort() != null && serverPorts.contains(appConfig.getTestRestPort())) { + return testWithDatabaseClient(appConfig.getHost(), appConfig.getTestRestPort(), + appConfig.getRestSslContext(), appConfig.getRestSecurityContextType(), + appConfig.getRestAdminUsername(), appConfig::newTestDatabaseClient); + } + return null; + } + + public static class TestResults { + private TestResult manageTestResult; + private TestResult adminTestResult; + private TestResult appServicesTestResult; + private TestResult restServerTestResult; + private TestResult testRestServerTestResult; + + public TestResults(TestResult manageTestResult, TestResult adminTestResult, + TestResult appServicesTestResult, TestResult restServerTestResult, TestResult testRestServerTestResult) { + this.manageTestResult = manageTestResult; + this.adminTestResult = adminTestResult; + this.appServicesTestResult = appServicesTestResult; + this.restServerTestResult = restServerTestResult; + this.testRestServerTestResult = testRestServerTestResult; + } + + public boolean anyTestFailed() { + return Stream.of(manageTestResult, adminTestResult, appServicesTestResult, restServerTestResult, testRestServerTestResult) + .anyMatch(test -> test != null && !test.isSucceeded()); + } + + public TestResult getManageTestResult() { + return manageTestResult; + } + + public TestResult getAdminTestResult() { + return adminTestResult; + } + + public TestResult getAppServicesTestResult() { + return appServicesTestResult; + } + + public TestResult getRestServerTestResult() { + return restServerTestResult; + } + + public TestResult getTestRestServerTestResult() { + return testRestServerTestResult; + } + + /** + * @return a multi-line summary of all the non-null test results. This is intended to provide a simple + * rendering of the test result data, suitable for use in ml-gradle. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Manage App Server\n").append(getManageTestResult()) + .append("\n\nAdmin App Server\n").append(getAdminTestResult()); + if (getManageTestResult().isSucceeded()) { + if (getAppServicesTestResult() != null) { + sb.append("\n\nApp-Services App Server\n").append(getAppServicesTestResult()); + } else { + sb.append("\n\nNo test run for the App-Services App Server as either a port is not configured for it or it has not been deployed yet"); + } + if (getRestServerTestResult() != null) { + sb.append("\n\nREST API App Server\n").append(getRestServerTestResult()); + } else { + sb.append("\n\nNo test run for a REST API App Server as either a port is not configured for it or it has not been deployed yet."); + } + if (getTestRestServerTestResult() != null) { + sb.append("\n\nTest REST API App Server\n").append(getTestRestServerTestResult()); + } else { + sb.append("\n\nNo test run for a Test REST API App Server as either a port is not configured for it or it has not been deployed yet."); + } + } else { + sb.append("\n\nCould not test connections against the App-Services or REST API App Servers " + + "due to the Manage App Server connection failing."); + } + return sb.toString(); + } + } + + public static class TestResult { + private String host; + private int port; + private String scheme; + private String authType; + private String username; + private boolean succeeded; + private String message; + + public TestResult(RestConfig restConfig, boolean succeeded, String message) { + this(restConfig.getHost(), restConfig.getPort(), restConfig.getScheme(), restConfig.getAuthType(), + restConfig.getUsername(), succeeded, message); + } + + public TestResult(RestConfig restConfig, Exception ex) { + this(restConfig, false, ex.getMessage()); + } + + public TestResult(String host, int port, String scheme, String authType, String username, DatabaseClient.ConnectionResult result) { + this.host = host; + this.port = port; + this.scheme = scheme; + this.authType = authType; + this.username = username; + this.succeeded = result.isConnected(); + if (!result.isConnected()) { + this.message = String.format("Received %d: %s", result.getStatusCode(), result.getErrorMessage()); + } + } + + public TestResult(String host, int port, String scheme, String authType, String username, boolean succeeded, String message) { + this.host = host; + this.port = port; + this.scheme = scheme; + this.authType = authType; + this.username = username; + this.succeeded = succeeded; + this.message = message; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public String getScheme() { + return scheme; + } + + public boolean isSucceeded() { + return succeeded; + } + + public String getMessage() { + return message; + } + + public String getAuthType() { + return authType; + } + + public String getUsername() { + return username; + } + + /** + * @return a multi-line representation of the test result. This is intended to provide a simple + * rendering of the test result data, suitable for use in ml-gradle. + */ + @Override + public String toString() { + String result = String.format("Configured to connect to %s://%s:%d using '%s' authentication", + getScheme(), getHost(), getPort(), getAuthType()); + if (getUsername() != null) { + result += String.format(" and username of '%s'", getUsername()); + } + if (isSucceeded()) { + result += "\nConnected successfully"; + return getMessage() != null ? result + "; " + getMessage() : result; + } + return result + "\nFAILED TO CONNECT; cause: " + message; + } + } + + private TestResult testManageAppServer(ManageClient client) { + ResponseErrorHandler originalErrorHandler = client.getRestTemplate().getErrorHandler(); + client.getRestTemplate().setErrorHandler(new DefaultResponseErrorHandler()); + try { + String version = new ClusterManager(client).getVersion(); + return new TestResult(client.getManageConfig(), true, "MarkLogic version: " + version); + } catch (Exception ex) { + if (ex instanceof HttpClientErrorException && ((HttpClientErrorException) ex).getRawStatusCode() == 404) { + return new TestResult(client.getManageConfig(), false, + "Unable to access /manage/v2; received 404; unexpected response: " + ex.getMessage()); + } else { + return new TestResult(client.getManageConfig(), ex); + } + } finally { + client.getRestTemplate().setErrorHandler(originalErrorHandler); + } + } + + private TestResult testAdminAppServer(AdminManager adminManager) { + try { + String timestamp = adminManager.getServerTimestamp(); + return new TestResult(adminManager.getAdminConfig(), true, "MarkLogic server timestamp: " + timestamp); + } catch (Exception ex) { + return new TestResult(adminManager.getAdminConfig(), ex); + } + } + + private TestResult testWithDatabaseClient(String host, Integer port, SSLContext sslContext, + SecurityContextType securityContextType, String username, Supplier supplier) { + if (port == null) { + return null; + } + final String scheme = sslContext != null ? "https" : "http"; + final String authType = securityContextType != null ? securityContextType.name().toLowerCase() : "unknown"; + DatabaseClient client = null; + try { + client = supplier.get(); + return new TestResult(host, port, scheme, authType, username, client.checkConnection()); + } catch (Exception ex) { + return new TestResult(host, port, scheme, authType, username, false, ex.getMessage()); + } finally { + if (client != null) { + client.release(); + } + } + } +} diff --git a/src/main/java/com/marklogic/appdeployer/command/data/LoadDataCommand.java b/src/main/java/com/marklogic/appdeployer/command/data/LoadDataCommand.java index 147ae6d3..35dd56a9 100644 --- a/src/main/java/com/marklogic/appdeployer/command/data/LoadDataCommand.java +++ b/src/main/java/com/marklogic/appdeployer/command/data/LoadDataCommand.java @@ -70,6 +70,9 @@ protected FileLoader buildFileLoader(AppConfig appConfig) { final DatabaseClient client = determineDatabaseClient(appConfig); final GenericFileLoader loader = new GenericFileLoader(client); + loader.setCascadeCollections(appConfig.isCascadeCollections()); + loader.setCascadePermissions(appConfig.isCascadePermissions()); + DataConfig dataConfig = appConfig.getDataConfig(); final Integer batchSize = dataConfig.getBatchSize(); diff --git a/src/main/java/com/marklogic/appdeployer/command/modules/DefaultModulesLoaderFactory.java b/src/main/java/com/marklogic/appdeployer/command/modules/DefaultModulesLoaderFactory.java index 4dae7558..c86fbf49 100644 --- a/src/main/java/com/marklogic/appdeployer/command/modules/DefaultModulesLoaderFactory.java +++ b/src/main/java/com/marklogic/appdeployer/command/modules/DefaultModulesLoaderFactory.java @@ -59,6 +59,8 @@ public ModulesLoader newModulesLoader(AppConfig appConfig) { RestBatchWriter assetBatchWriter = new RestBatchWriter(modulesDatabaseClient, false); assetBatchWriter.setThreadCount(threadCount); AssetFileLoader assetFileLoader = new AssetFileLoader(assetBatchWriter, modulesManager); + assetFileLoader.setCascadeCollections(appConfig.isCascadeCollections()); + assetFileLoader.setCascadePermissions(appConfig.isCascadePermissions()); if (appConfig.getModulesLoaderBatchSize() != null) { assetFileLoader.setBatchSize(appConfig.getModulesLoaderBatchSize()); } diff --git a/src/main/java/com/marklogic/appdeployer/command/schemas/LoadSchemasCommand.java b/src/main/java/com/marklogic/appdeployer/command/schemas/LoadSchemasCommand.java index 7c64cf3b..7eb2810c 100644 --- a/src/main/java/com/marklogic/appdeployer/command/schemas/LoadSchemasCommand.java +++ b/src/main/java/com/marklogic/appdeployer/command/schemas/LoadSchemasCommand.java @@ -80,9 +80,10 @@ protected void loadSchemasFromDatabaseSpecificPaths(CommandContext context) { protected void loadSchemas(String schemasPath, String schemasDatabaseName, CommandContext context) { logger.info(format("Loading schemas into database %s from: %s", schemasDatabaseName, schemasPath)); - DatabaseClient client = buildDatabaseClient(schemasDatabaseName, context); + DatabaseClient schemasClient = context.getAppConfig().newAppServicesDatabaseClient(schemasDatabaseName); + DatabaseClient contentClient = buildContentClient(context, schemasDatabaseName); try { - SchemasLoader schemasLoader = buildSchemasLoader(context, client, schemasDatabaseName); + SchemasLoader schemasLoader = buildSchemasLoader(context, schemasClient, contentClient); schemasLoader.loadSchemas(schemasPath); logger.info("Finished loading schemas from: " + schemasPath); } catch (FailedRequestException fre) { @@ -92,12 +93,29 @@ protected void loadSchemas(String schemasPath, String schemasDatabaseName, Comma throw fre; } } finally { - client.release(); + schemasClient.release(); + if (contentClient != null) { + contentClient.release(); + } } } - protected DatabaseClient buildDatabaseClient(String schemasDatabaseName, CommandContext context) { - return context.getAppConfig().newAppServicesDatabaseClient(schemasDatabaseName); + /** + * Construct a content client, for use when validating TDEs and generating QBVs. + * + * @param context + * @param schemasDatabase + * @return + */ + private DatabaseClient buildContentClient(CommandContext context, String schemasDatabase) { + String contentDatabase = findContentDatabaseAssociatedWithSchemasDatabase(context, schemasDatabase); + if (contentDatabase != null) { + logger.info(format("Will use %s as a content database when loading into schemas database: %s", contentDatabase, schemasDatabase)); + return context.getAppConfig().newAppServicesDatabaseClient(contentDatabase); + } + logger.warn(format("Unable to find a content database associated with schemas database: %s; this may " + + "result in errors when loading TDE templates and Query-Based-View scripts.", schemasDatabase)); + return null; } /** @@ -106,24 +124,14 @@ protected DatabaseClient buildDatabaseClient(String schemasDatabaseName, Command * So given a schemasDatabaseName, * * @param context - * @param client + * @param schemasClient * @return */ - protected SchemasLoader buildSchemasLoader(CommandContext context, DatabaseClient client, String schemasDatabaseName) { + protected SchemasLoader buildSchemasLoader(CommandContext context, DatabaseClient schemasClient, DatabaseClient contentClient) { AppConfig appConfig = context.getAppConfig(); - - String tdeValidationDatabase = null; - if (appConfig.isTdeValidationEnabled()) { - tdeValidationDatabase = findContentDatabaseAssociatedWithSchemasDatabase(context, schemasDatabaseName); - if (tdeValidationDatabase != null) { - logger.info(format("TDE templates loaded into %s will be validated against content database %s", - schemasDatabaseName, tdeValidationDatabase)); - } - } else { - logger.info("TDE validation is disabled"); - } - - DefaultSchemasLoader schemasLoader = new DefaultSchemasLoader(client, tdeValidationDatabase); + DefaultSchemasLoader schemasLoader = new DefaultSchemasLoader(schemasClient, contentClient, context.getAppConfig().isTdeValidationEnabled()); + schemasLoader.setCascadeCollections(appConfig.isCascadeCollections()); + schemasLoader.setCascadePermissions(appConfig.isCascadePermissions()); FileFilter filter = appConfig.getSchemasFileFilter(); if (filter != null) { schemasLoader.addFileFilter(filter); diff --git a/src/main/java/com/marklogic/appdeployer/scaffold/ScaffoldGenerator.java b/src/main/java/com/marklogic/appdeployer/scaffold/ScaffoldGenerator.java index ae10e445..c997a7e6 100644 --- a/src/main/java/com/marklogic/appdeployer/scaffold/ScaffoldGenerator.java +++ b/src/main/java/com/marklogic/appdeployer/scaffold/ScaffoldGenerator.java @@ -38,7 +38,7 @@ public class ScaffoldGenerator extends LoggingObject { protected ObjectMapper objectMapper; private PrettyPrinter prettyPrinter = new DefaultPrettyPrinter(); - public void generateScaffold(String path, AppConfig config) { + public void generateScaffold(String path, AppInputs appInputs) { if (objectMapper == null) { objectMapper = ObjectMapperFactory.getObjectMapper(); } @@ -50,29 +50,58 @@ public void generateScaffold(String path, AppConfig config) { File modulesDir = getModulesDir(rootDir); modulesDir.mkdirs(); - generateContentDatabaseFile(configDir, config); - generateSecurityFiles(configDir, config); + generateDatabaseFiles(configDir); + if (appInputs.isWithUsersAndRoles()) { + generateSecurityFiles(configDir, appInputs.getAppName()); + } + if (appInputs.isWithRestServer()) { + generateRestApiFile(configDir); + generateRestPropertiesFile(modulesDir); + generateSearchOptions(modulesDir, appInputs.getAppName()); + } + } + /** + * + * @param path + * @param config + * @deprecated since 4.6.0; use the method using {@code ScaffoldInputs} instead. + */ + @Deprecated + public void generateScaffold(String path, AppConfig config) { + if (objectMapper == null) { + objectMapper = ObjectMapperFactory.getObjectMapper(); + } + File rootDir = new File(path); + + File configDir = getConfigDir(rootDir); + configDir.mkdirs(); + + File modulesDir = getModulesDir(rootDir); + modulesDir.mkdirs(); + + generateDatabaseFiles(configDir); + generateSecurityFiles(configDir, config.getName()); if (!config.isNoRestServer()) { - generateRestApiFile(configDir, config); - generateRestPropertiesFile(modulesDir, config); - generateSearchOptions(modulesDir, config); + generateRestApiFile(configDir); + generateRestPropertiesFile(modulesDir); + generateSearchOptions(modulesDir, config.getName()); } } - private void generateSearchOptions(File modulesDir, AppConfig config) { + private void generateSearchOptions(File modulesDir, String appName) { File optionsDir = new File(modulesDir, "options"); optionsDir.mkdirs(); String xml = "\n unfiltered\n 0\n"; - writeFile(xml.getBytes(), new File(optionsDir, config.getName() + "-options.xml")); + writeFile(xml.getBytes(), new File(optionsDir, appName + "-options.xml")); } - protected void generateRestPropertiesFile(File modulesDir, AppConfig config) { - writeFile(buildRestPropertiesJson(config), new File(modulesDir, "rest-properties.json")); + protected void generateRestPropertiesFile(File modulesDir) { + writeFile(buildRestPropertiesJson(), new File(modulesDir, "rest-properties.json")); } - protected ObjectNode buildRestPropertiesJson(AppConfig config) { + protected ObjectNode buildRestPropertiesJson() { ObjectNode node = objectMapper.createObjectNode(); node.put("debug", false); node.put("validate-queries", true); @@ -81,70 +110,71 @@ protected ObjectNode buildRestPropertiesJson(AppConfig config) { return node; } - protected void generateSecurityFiles(File configDir, AppConfig config) { + protected void generateSecurityFiles(File configDir, String appName) { File rolesDir = new File(configDir, "security/roles"); rolesDir.mkdirs(); - writeFile(buildNobodyRole(config), new File(rolesDir, "1-" + config.getName() + "-nobody-role.json")); - writeFile(buildReaderRole(config), new File(rolesDir, "2-" + config.getName() + "-reader-role.json")); - writeFile(buildWriterRole(config), new File(rolesDir, "3-" + config.getName() + "-writer-role.json")); - writeFile(buildInternalRole(config), new File(rolesDir, "4-" + config.getName() + "-internal-role.json")); - writeFile(buildAdminRole(config), new File(rolesDir, "5-" + config.getName() + "-admin-role.json")); + writeFile(buildNobodyRole(appName), new File(rolesDir, "1-" + appName + "-nobody-role.json")); + writeFile(buildReaderRole(appName), new File(rolesDir, "2-" + appName + "-reader-role.json")); + writeFile(buildWriterRole(appName), new File(rolesDir, "3-" + appName + "-writer-role.json")); + writeFile(buildInternalRole(appName), new File(rolesDir, "4-" + appName + "-internal-role.json")); + writeFile(buildAdminRole(appName), new File(rolesDir, "5-" + appName + "-admin-role.json")); File usersDir = new File(configDir, "security/users"); usersDir.mkdirs(); - writeFile(buildReaderUser(config), new File(usersDir, config.getName() + "-reader-user.json")); - writeFile(buildWriterUser(config), new File(usersDir, config.getName() + "-writer-user.json")); - writeFile(buildAdminUser(config), new File(usersDir, config.getName() + "-admin-user.json")); + writeFile(buildReaderUser(appName), new File(usersDir, appName + "-reader-user.json")); + writeFile(buildWriterUser(appName), new File(usersDir, appName + "-writer-user.json")); + writeFile(buildAdminUser(appName), new File(usersDir, appName + "-admin-user.json")); } - protected ObjectNode buildNobodyRole(AppConfig config) { + protected ObjectNode buildNobodyRole(String appName) { ObjectNode node = objectMapper.createObjectNode(); - node.put("role-name", config.getName() + "-nobody"); + node.put("role-name", appName + "-nobody"); node.put("description", "Unauthenticated user"); node.putArray("role"); return node; } - protected ObjectNode buildReaderRole(AppConfig config) { + protected ObjectNode buildReaderRole(String appName) { ObjectNode node = objectMapper.createObjectNode(); - node.put("role-name", config.getName() + "-reader"); + node.put("role-name", appName + "-reader"); node.put("description", "Can view documents, but not edit"); ArrayNode array = node.putArray("role"); - array.add("rest-reader"); - array.add(config.getName() + "-nobody"); + array.add(appName + "-nobody"); + array = node.putArray("privilege"); + array.add(buildPrivilege("rest-reader", "http://marklogic.com/xdmp/privileges/rest-reader", "execute")); return node; } - protected ObjectNode buildWriterRole(AppConfig config) { + protected ObjectNode buildWriterRole(String appName) { ObjectNode node = objectMapper.createObjectNode(); - node.put("role-name", config.getName() + "-writer"); + node.put("role-name", appName + "-writer"); node.put("description", "Can read and write documents"); ArrayNode array = node.putArray("role"); - array.add("rest-writer"); - array.add(config.getName() + "-reader"); + array.add(appName + "-reader"); array = node.putArray("privilege"); + array.add(buildPrivilege("rest-writer", "http://marklogic.com/xdmp/privileges/rest-writer", "execute")); array.add(buildPrivilege("any-uri", "http://marklogic.com/xdmp/privileges/any-uri", "execute")); array.add(buildPrivilege("unprotected-collections", "http://marklogic.com/xdmp/privileges/unprotected-collections", "execute")); return node; } - protected ObjectNode buildInternalRole(AppConfig config) { + protected ObjectNode buildInternalRole(String appName) { ObjectNode node = objectMapper.createObjectNode(); - node.put("role-name", config.getName() + "-internal"); + node.put("role-name", appName + "-internal"); node.put("description", "Internal role used for amping"); ArrayNode array = node.putArray("role"); - array.add(config.getName() + "-writer"); + array.add(appName + "-writer"); return node; } - protected ObjectNode buildAdminRole(AppConfig config) { + protected ObjectNode buildAdminRole(String appName) { ObjectNode node = objectMapper.createObjectNode(); - node.put("role-name", config.getName() + "-admin"); + node.put("role-name", appName + "-admin"); node.put("description", "Non-admin administrator"); ArrayNode array = node.putArray("role"); array.add("rest-admin"); array.add("manage-admin"); - array.add(config.getName() + "-writer"); + array.add(appName + "-writer"); array = node.putArray("privilege"); array.add(buildPrivilege("any-uri", "http://marklogic.com/xdmp/privileges/any-uri", "execute")); array.add(buildPrivilege("xdbc:insert-in", "http://marklogic.com/xdmp/privileges/xdbc-insert-in", "execute")); @@ -160,54 +190,56 @@ protected ObjectNode buildPrivilege(String name, String action, String kind) { return node; } - protected ObjectNode buildReaderUser(AppConfig config) { + protected ObjectNode buildReaderUser(String appName) { ObjectNode node = objectMapper.createObjectNode(); - String name = config.getName() + "-reader"; + String name = appName + "-reader"; node.put("user-name", name); node.put("password", name); ArrayNode roles = node.putArray("role"); - roles.add(config.getName() + "-reader"); + roles.add(appName + "-reader"); return node; } - protected ObjectNode buildWriterUser(AppConfig config) { + protected ObjectNode buildWriterUser(String appName) { ObjectNode node = objectMapper.createObjectNode(); - String name = config.getName() + "-writer"; + String name = appName + "-writer"; node.put("user-name", name); node.put("password", name); ArrayNode roles = node.putArray("role"); - roles.add(config.getName() + "-writer"); + roles.add(appName + "-writer"); return node; } - protected ObjectNode buildAdminUser(AppConfig config) { + protected ObjectNode buildAdminUser(String appName) { ObjectNode node = objectMapper.createObjectNode(); - String name = config.getName() + "-admin"; + String name = appName + "-admin"; node.put("user-name", name); node.put("password", name); ArrayNode roles = node.putArray("role"); - roles.add(config.getName() + "-admin"); + roles.add(appName + "-admin"); return node; } - protected void generateRestApiFile(File configDir, AppConfig config) { - writeFile(buildRestApiJson(config).getBytes(), new File(configDir, "rest-api.json")); + protected void generateRestApiFile(File configDir) { + writeFile(buildRestApiJson().getBytes(), new File(configDir, "rest-api.json")); } - protected String buildRestApiJson(AppConfig config) { + protected String buildRestApiJson() { return RestApiUtil.buildDefaultRestApiJson(); } - protected void generateContentDatabaseFile(File configDir, AppConfig config) { + protected void generateDatabaseFiles(File configDir) { File databasesDir = new File(configDir, "databases"); databasesDir.mkdirs(); - writeFile(buildContentDatabaseJson(config), new File(databasesDir, "content-database.json")); + writeFile(buildContentDatabaseJson(), new File(databasesDir, "content-database.json")); + writeFile(buildSchemasDatabaseJson(), new File(databasesDir, "schemas-database.json")); } - protected ObjectNode buildContentDatabaseJson(AppConfig config) { + protected ObjectNode buildContentDatabaseJson() { ObjectNode node = objectMapper.createObjectNode(); node.put("database-name", "%%DATABASE%%"); + node.put("schema-database", "%%SCHEMAS_DATABASE%%"); ArrayNode array = node.putArray("range-element-index"); ObjectNode index = array.addObject(); index.put("scalar-type", "string"); @@ -219,6 +251,12 @@ protected ObjectNode buildContentDatabaseJson(AppConfig config) { return node; } + protected ObjectNode buildSchemasDatabaseJson() { + ObjectNode node = objectMapper.createObjectNode(); + node.put("database-name", "%%SCHEMAS_DATABASE%%"); + return node; + } + protected void writeFile(ObjectNode node, File f) { try { byte[] bytes = objectMapper.writer(prettyPrinter).writeValueAsBytes(node); @@ -258,4 +296,32 @@ public void setObjectMapper(ObjectMapper objectMapper) { public void setPrettyPrinter(PrettyPrinter prettyPrinter) { this.prettyPrinter = prettyPrinter; } + + public static class AppInputs { + final private String appName; + final private boolean withRestServer; + final private boolean withUsersAndRoles; + + public AppInputs(String appName) { + this(appName, true, true); + } + + public AppInputs(String appName, boolean withRestServer, boolean withUsersAndRoles) { + this.appName = appName; + this.withRestServer = withRestServer; + this.withUsersAndRoles = withUsersAndRoles; + } + + public String getAppName() { + return appName; + } + + public boolean isWithRestServer() { + return withRestServer; + } + + public boolean isWithUsersAndRoles() { + return withUsersAndRoles; + } + } } diff --git a/src/main/java/com/marklogic/mgmt/admin/AdminManager.java b/src/main/java/com/marklogic/mgmt/admin/AdminManager.java index 6c2ebe8d..570f4ad2 100644 --- a/src/main/java/com/marklogic/mgmt/admin/AdminManager.java +++ b/src/main/java/com/marklogic/mgmt/admin/AdminManager.java @@ -236,6 +236,15 @@ public String getServerVersion() { return getServerConfig().getElementValue("/m:host/m:version"); } + /** + * + * @return + * @since 4.6.0 + */ + public String getServerTimestamp() { + return getServerConfig().getElementValue("/m:host/m:timestamp"); + } + public void setWaitForRestartCheckInterval(int waitForRestartCheckInterval) { this.waitForRestartCheckInterval = waitForRestartCheckInterval; } diff --git a/src/main/java/com/marklogic/mgmt/cma/ConfigurationManager.java b/src/main/java/com/marklogic/mgmt/cma/ConfigurationManager.java index 4017cb2b..62429dc7 100644 --- a/src/main/java/com/marklogic/mgmt/cma/ConfigurationManager.java +++ b/src/main/java/com/marklogic/mgmt/cma/ConfigurationManager.java @@ -15,17 +15,20 @@ */ package com.marklogic.mgmt.cma; +import com.fasterxml.jackson.databind.JsonNode; import com.marklogic.mgmt.AbstractManager; import com.marklogic.mgmt.ManageClient; import com.marklogic.mgmt.SaveReceipt; import com.marklogic.rest.util.MgmtResponseErrorHandler; +import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.util.UriComponentsBuilder; /** * This doesn't extend AbstractResourceManager because a configuration isn't really a resource, it's a collection of * resources. - * + *

* Currently only supports JSON and XML configuration payloads. Not clear yet from the docs on what the format of a * zip should be. The docs also mention a bunch of request parameters, but the examples don't show what the purpose * of those are, so those aren't supported yet either. @@ -48,6 +51,7 @@ protected boolean useSecurityUser() { /** * Returns true if the CMA endpoint exists. This temporarily disables logging in MgmtResponseErrorHandler so that * a client doesn't see the 404 error being logged, which could be mistakenly perceived as a real error. + * * @return */ public boolean endpointExists() { @@ -96,4 +100,19 @@ public SaveReceipt submit(String payload) { return new SaveReceipt(null, payload, PATH, response); } + /** + * @param resourceType + * @return a JSON response containing details on each resource of the given type + * @since 4.6.0 + */ + public ResponseEntity getResourcesAsJson(String resourceType) { + String uri = UriComponentsBuilder + .fromUri(manageClient.buildUri("/manage/v3")) + .queryParam("format", "json") + .queryParam("resource-type", resourceType) + .encode() + .toUriString(); + + return manageClient.getRestTemplate().exchange(uri, HttpMethod.GET, null, JsonNode.class); + } } diff --git a/src/main/java/com/marklogic/mgmt/resource/forests/ForestManager.java b/src/main/java/com/marklogic/mgmt/resource/forests/ForestManager.java index c957ac0a..112c508c 100644 --- a/src/main/java/com/marklogic/mgmt/resource/forests/ForestManager.java +++ b/src/main/java/com/marklogic/mgmt/resource/forests/ForestManager.java @@ -20,6 +20,7 @@ import com.marklogic.mgmt.ManageClient; import com.marklogic.mgmt.api.API; import com.marklogic.mgmt.api.forest.Forest; +import com.marklogic.mgmt.cma.ConfigurationManager; import com.marklogic.mgmt.mapper.DefaultResourceMapper; import com.marklogic.mgmt.mapper.ResourceMapper; import com.marklogic.mgmt.resource.AbstractResourceManager; @@ -67,11 +68,7 @@ public ForestManager(ManageClient client) { * @since 4.5.3 */ public Map> getMapOfPrimaryForests() { - String uri = UriComponentsBuilder.fromUri(getManageClient().buildUri("/manage/v3")) - .queryParam("format", "json").queryParam("resource-type", "forest") - .encode().toUriString(); - - JsonNode json = getManageClient().getRestTemplate().exchange(uri, HttpMethod.GET, null, JsonNode.class).getBody(); + JsonNode json = new ConfigurationManager(getManageClient()).getResourcesAsJson("forest").getBody(); // Config is an array of objects, and it will have a single object based on our request. ArrayNode allPrimaryForests = (ArrayNode) json.get("config").get(0).get("forest"); ResourceMapper mapper = new DefaultResourceMapper(new API(getManageClient())); diff --git a/src/main/java/com/marklogic/mgmt/resource/restapis/RestApiManager.java b/src/main/java/com/marklogic/mgmt/resource/restapis/RestApiManager.java index d7e607ce..1cf9fadc 100644 --- a/src/main/java/com/marklogic/mgmt/resource/restapis/RestApiManager.java +++ b/src/main/java/com/marklogic/mgmt/resource/restapis/RestApiManager.java @@ -105,13 +105,24 @@ public boolean deleteRestApi(RestApiDeletionRequest request) { PayloadParser parser = new PayloadParser(); if (request.isIncludeModules()) { + boolean includeModules = true; if (request.isDeleteModulesReplicaForests()) { - String modulesDatabase = parser.getPayloadFieldValue(payload, "modules-database"); - if (databaseManager.exists(modulesDatabase)) { + String modulesDatabase = null; + try { + modulesDatabase = parser.getPayloadFieldValue(payload, "modules-database"); + } catch (Exception e) { + logger.warn("Unable to get value of `modules-database`; will not be able to delete " + + "modules database. This may be expected if the modules database has been set to " + + "'filesystem' for the app server. Error: {}", e.getMessage()); + includeModules = false; + } + if (modulesDatabase != null && databaseManager.exists(modulesDatabase)) { databaseManager.deleteReplicaForests(modulesDatabase); } } - path += "include=modules&"; + if (includeModules) { + path += "include=modules&"; + } } if (request.isIncludeContent()) { diff --git a/src/main/java/com/marklogic/rest/util/MgmtResponseErrorHandler.java b/src/main/java/com/marklogic/rest/util/MgmtResponseErrorHandler.java index afcbf8bc..976a9cad 100644 --- a/src/main/java/com/marklogic/rest/util/MgmtResponseErrorHandler.java +++ b/src/main/java/com/marklogic/rest/util/MgmtResponseErrorHandler.java @@ -18,6 +18,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; +import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.client.ClientHttpResponse; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.HttpClientErrorException; @@ -49,6 +50,14 @@ public void handleError(ClientHttpResponse response) throws IOException { logger.error(message); } throw ex; + } catch (InvalidMediaTypeException ex) { + // In at least one scenario - when deleting a REST API server whose modules database has been set to be + // the filesystem (which is not a valid setup, but a user may still do it), MarkLogic returns a mime type + // containing commas - e.g. "text/plain, application/json". And Spring does not like that and throws this + // error. That obscures the actual error. So a runtime exception is thrown with the mime type error but + // also the response body from MarkLogic, which will contain the actual error. + String body = new String(getResponseBody(response)); + throw new RuntimeException("Unable to parse mime type: " + ex.getMessage() + "; response body from MarkLogic: " + body); } } diff --git a/src/main/java/com/marklogic/rest/util/RestConfig.java b/src/main/java/com/marklogic/rest/util/RestConfig.java index ca3b770a..4152e40e 100644 --- a/src/main/java/com/marklogic/rest/util/RestConfig.java +++ b/src/main/java/com/marklogic/rest/util/RestConfig.java @@ -125,7 +125,7 @@ public DatabaseClientBuilder newDatabaseClientBuilder() { @Override public String toString() { - return String.format("%s://%shost:%d", getScheme(), getHost(), getPort()); + return String.format("%s://%s:%d", getScheme(), getHost(), getPort()); } /** diff --git a/src/test/java/com/marklogic/appdeployer/AbstractAppDeployerTest.java b/src/test/java/com/marklogic/appdeployer/AbstractAppDeployerTest.java index 74674978..670360d9 100644 --- a/src/test/java/com/marklogic/appdeployer/AbstractAppDeployerTest.java +++ b/src/test/java/com/marklogic/appdeployer/AbstractAppDeployerTest.java @@ -15,12 +15,17 @@ */ package com.marklogic.appdeployer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.marklogic.appdeployer.command.Command; +import com.marklogic.appdeployer.command.CommandContext; import com.marklogic.appdeployer.command.modules.DefaultModulesLoaderFactory; import com.marklogic.appdeployer.command.modules.LoadModulesCommand; +import com.marklogic.appdeployer.command.security.GenerateTemporaryCertificateCommand; import com.marklogic.appdeployer.impl.SimpleAppDeployer; import com.marklogic.client.ext.modulesloader.impl.DefaultModulesLoader; import com.marklogic.mgmt.AbstractMgmtTest; +import com.marklogic.mgmt.resource.appservers.ServerManager; import com.marklogic.xcc.template.XccTemplate; import org.junit.jupiter.api.BeforeEach; @@ -108,4 +113,39 @@ protected LoadModulesCommand buildLoadModulesCommand() { protected void setConfigBaseDir(String path) { appConfig.getFirstConfigDir().setBaseDir(new File("src/test/resources/" + path)); } + + /** + * Intended to simplify testing app servers that require SSL. + */ + protected final void configureRestServersToRequireSSL() { + GenerateTemporaryCertificateCommand gtcc = new GenerateTemporaryCertificateCommand(); + gtcc.setTemplateIdOrName("sample-app-template"); + gtcc.execute(new CommandContext(appConfig, manageClient, adminManager)); + + ObjectNode payload = new ObjectMapper().createObjectNode() + .put("server-name", SAMPLE_APP_NAME) + .put("group-name", "Default") + .put("ssl-certificate-template", "sample-app-template"); + + ServerManager mgr = new ServerManager(manageClient); + mgr.save(payload.toString()); + payload.put("server-name", SAMPLE_APP_NAME + "-test"); + mgr.save(payload.toString()); + } + + protected final void configureRestServersToNotRequireSSL() { + ObjectNode payload = new ObjectMapper().createObjectNode() + .put("server-name", SAMPLE_APP_NAME) + .put("group-name", "Default") + .put("ssl-certificate-template", ""); + + ServerManager mgr = new ServerManager(manageClient); + mgr.save(payload.toString()); + payload.put("server-name", SAMPLE_APP_NAME + "-test"); + mgr.save(payload.toString()); + } + + protected final CommandContext newCommandContext() { + return new CommandContext(appConfig, manageClient, adminManager); + } } diff --git a/src/test/java/com/marklogic/appdeployer/DefaultAppConfigFactoryTest.java b/src/test/java/com/marklogic/appdeployer/DefaultAppConfigFactoryTest.java index 27cdbeae..46db8757 100644 --- a/src/test/java/com/marklogic/appdeployer/DefaultAppConfigFactoryTest.java +++ b/src/test/java/com/marklogic/appdeployer/DefaultAppConfigFactoryTest.java @@ -393,6 +393,10 @@ public void mostProperties() { p.setProperty("mlUpdateMimetypeWhenPropertiesAreEqual", "true"); + // 4.6.0 + p.setProperty("mlCascadeCollections", "true"); + p.setProperty("mlCascadePermissions", "true"); + sut = new DefaultAppConfigFactory(new SimplePropertySource(p)); AppConfig config = sut.newAppConfig(); @@ -565,6 +569,9 @@ public void mostProperties() { assertEquals("other-group", map.get("host2")); assertTrue(config.isUpdateMimetypeWhenPropertiesAreEqual()); + + assertTrue(config.isCascadeCollections()); + assertTrue(config.isCascadePermissions()); } /** diff --git a/src/test/java/com/marklogic/appdeployer/command/TestConnectionsTest.java b/src/test/java/com/marklogic/appdeployer/command/TestConnectionsTest.java new file mode 100644 index 00000000..fd59e050 --- /dev/null +++ b/src/test/java/com/marklogic/appdeployer/command/TestConnectionsTest.java @@ -0,0 +1,208 @@ +package com.marklogic.appdeployer.command; + +import com.marklogic.appdeployer.AbstractAppDeployerTest; +import com.marklogic.appdeployer.command.restapis.DeployRestApiServersCommand; +import com.marklogic.appdeployer.command.security.DeployCertificateTemplatesCommand; +import com.marklogic.mgmt.ManageClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestConnectionsTest extends AbstractAppDeployerTest { + + private TestConnectionsCommand command = new TestConnectionsCommand(); + + @AfterEach + void afterEach() { + undeploySampleApp(); + } + + /** + * Multiple scenarios are tested here as they can all use the same deployed app. + */ + @Test + void allConnectionsSucceed() { + appConfig.setTestRestPort(SAMPLE_APP_TEST_REST_PORT); + initializeAppDeployer(new DeployRestApiServersCommand(), new DeployCertificateTemplatesCommand()); + deploySampleApp(); + + verifyAllConnectionsSucceed(); + verifyConnectionsSucceedWhenRestServersRequireSSL(); + verifyResultsWhenManageConnectionFails(); + } + + /** + * In this scenario, the 3 REST API app servers don't exist yet - which is fine, no error should be thrown, + * we just don't get any test results for them. + */ + @Test + void restServersDontExist() { + appConfig.setAppServicesPort(SAMPLE_APP_REST_PORT); + appConfig.setTestRestPort(SAMPLE_APP_TEST_REST_PORT); + + CommandContext context = new CommandContext(appConfig, manageClient, adminManager); + TestConnectionsCommand command = new TestConnectionsCommand(); + // Smoke test, just expecting logging + command.execute(context); + + TestConnectionsCommand.TestResults results = command.testConnections(context); + assertFalse(results.anyTestFailed()); + + assertTrue(results.getManageTestResult().isSucceeded()); + assertTrue(results.getAdminTestResult().isSucceeded()); + assertNull(results.getAppServicesTestResult()); + assertNull(results.getRestServerTestResult()); + assertNull(results.getTestRestServerTestResult()); + } + + /** + * In this scenario, the Manage test fails, meaning we can't test any of the REST API app servers since we can't + * check to see if they exist or not via the Manage app server. + */ + private void verifyResultsWhenManageConnectionFails() { + final String validPassword = manageConfig.getPassword(); + try { + manageConfig.setPassword("Wrong password"); + TestConnectionsCommand.TestResults results = + command.testConnections(new CommandContext(appConfig, new ManageClient(manageConfig), adminManager)); + + assertTrue(results.anyTestFailed()); + assertFalse(results.getManageTestResult().isSucceeded()); + assertTrue(results.getAdminTestResult().isSucceeded()); + assertNull(results.getAppServicesTestResult()); + assertNull(results.getRestServerTestResult()); + assertNull(results.getTestRestServerTestResult()); + } finally { + manageConfig.setPassword(validPassword); + } + } + + private void verifyConnectionsSucceedWhenRestServersRequireSSL() { + appConfig.setSimpleSslConfig(); + configureRestServersToRequireSSL(); + try { + TestConnectionsCommand.TestResults results = + command.testConnections(new CommandContext(appConfig, manageClient, adminManager)); + assertFalse(results.anyTestFailed()); + + TestConnectionsCommand.TestResult restResult = results.getRestServerTestResult(); + assertEquals("https", restResult.getScheme()); + assertTrue(restResult.toString().startsWith( + "Configured to connect to https://localhost:8004 using 'digest' authentication and username of 'admin'"), + "Unexpected message: " + restResult); + + TestConnectionsCommand.TestResult testRestResult = results.getTestRestServerTestResult(); + assertEquals("https", testRestResult.getScheme()); + assertTrue(testRestResult.toString().startsWith( + "Configured to connect to https://localhost:8005 using 'digest' authentication and username of 'admin'"), + "Unexpected message: " + testRestResult); + + // Disable SSL on the client side and verify we get errors + appConfig.setRestSslContext(null); + appConfig.setRestTrustManager(null); + appConfig.setRestSslHostnameVerifier(null); + + results = command.testConnections(new CommandContext(appConfig, manageClient, adminManager)); + restResult = results.getRestServerTestResult(); + assertFalse(restResult.isSucceeded()); + assertEquals("Received 403: Forbidden", restResult.getMessage(), "Unfortunately, the Java Client receives " + + "nothing indicating an SSL issue; the request doesn't even show up in the app server's AccessLog. " + + "All the user gets is a 403 back when SSL is required by the client is not using it."); + testRestResult = results.getTestRestServerTestResult(); + assertFalse(testRestResult.isSucceeded()); + assertEquals("Received 403: Forbidden", testRestResult.getMessage()); + } finally { + configureRestServersToNotRequireSSL(); + appConfig.setRestSslContext(null); + appConfig.setRestTrustManager(null); + appConfig.setRestSslHostnameVerifier(null); + } + } + + private void verifyAllConnectionsSucceed() { + CommandContext context = new CommandContext(appConfig, manageClient, adminManager); + + // Smoke test - this just logs text on success. + command.execute(context); + TestConnectionsCommand.TestResults results = command.testConnections(context); + assertFalse(results.anyTestFailed()); + + final String host = manageConfig.getHost(); + + TestConnectionsCommand.TestResult manageResult = results.getManageTestResult(); + assertEquals(host, manageResult.getHost()); + assertEquals(manageConfig.getPort(), manageResult.getPort()); + assertEquals("http", manageResult.getScheme()); + assertEquals(manageConfig.getUsername(), manageResult.getUsername()); + assertEquals(manageConfig.getAuthType(), manageResult.getAuthType()); + assertTrue(manageResult.isSucceeded()); + assertTrue(manageResult.getMessage().startsWith("MarkLogic version:"), + "Unexpected message; the MarkLogic version should be shown as a quick confirmation of the version of " + + "MarkLogic that the user is connecting to; actual message: " + manageResult.getMessage()); + assertTrue( + manageResult.toString().startsWith("Configured to connect to http://localhost:8002 using 'digest' authentication and username of 'admin'"), + "Unexpected toString content: " + manageResult + ); + + TestConnectionsCommand.TestResult adminResult = results.getAdminTestResult(); + assertEquals(host, adminResult.getHost()); + assertEquals(adminConfig.getPort(), adminResult.getPort()); + assertEquals("http", adminResult.getScheme()); + assertEquals(adminConfig.getUsername(), adminResult.getUsername()); + assertEquals(adminConfig.getAuthType(), adminResult.getAuthType()); + assertTrue(adminResult.isSucceeded()); + assertTrue(adminResult.getMessage().startsWith("MarkLogic server timestamp:"), + "Unexpected message; the server timestamp should be shown as a quick confirmation of the timezone that " + + "the MarkLogic instance is running in; actual message: " + adminResult.getMessage()); + assertTrue( + adminResult.toString().startsWith("Configured to connect to http://localhost:8001 using 'digest' authentication and username of 'admin'"), + "Unexpected toString content: " + adminResult + ); + + TestConnectionsCommand.TestResult appServicesResult = results.getAppServicesTestResult(); + assertEquals(host, appServicesResult.getHost()); + assertEquals(appConfig.getAppServicesPort(), appServicesResult.getPort()); + assertEquals("http", appServicesResult.getScheme()); + assertEquals(appConfig.getAppServicesUsername(), appServicesResult.getUsername()); + assertEquals(appConfig.getAppServicesSecurityContextType().name().toLowerCase(), appServicesResult.getAuthType()); + assertTrue(appServicesResult.isSucceeded()); + assertNull(appServicesResult.getMessage(), "The Java Client doesn't provide any success message when " + + "checkConnection succeeds."); + assertTrue( + appServicesResult.toString().startsWith("Configured to connect to http://localhost:8000 using 'digest' authentication and username of 'admin'"), + "Unexpected toString content: " + appServicesResult + ); + + TestConnectionsCommand.TestResult restResult = results.getRestServerTestResult(); + assertEquals(host, restResult.getHost()); + assertEquals(appConfig.getRestPort(), restResult.getPort()); + assertEquals("http", restResult.getScheme()); + assertEquals(appConfig.getRestAdminUsername(), restResult.getUsername()); + assertEquals(appConfig.getRestSecurityContextType().name().toLowerCase(), restResult.getAuthType()); + assertTrue(restResult.isSucceeded()); + assertNull(restResult.getMessage(), "The Java Client doesn't provide any success message when " + + "checkConnection succeeds."); + assertTrue( + restResult.toString().startsWith("Configured to connect to http://localhost:8004 using 'digest' authentication and username of 'admin'"), + "Unexpected toString content: " + restResult + ); + + TestConnectionsCommand.TestResult testRestResult = results.getTestRestServerTestResult(); + assertEquals(host, testRestResult.getHost()); + assertEquals(appConfig.getTestRestPort(), testRestResult.getPort()); + assertEquals("http", testRestResult.getScheme()); + assertEquals(appConfig.getRestAdminUsername(), testRestResult.getUsername()); + assertEquals(appConfig.getRestSecurityContextType().name().toLowerCase(), testRestResult.getAuthType()); + assertTrue(testRestResult.isSucceeded()); + assertNull(testRestResult.getMessage(), "The Java Client doesn't provide any success message when " + + "checkConnection succeeds."); + assertTrue( + testRestResult.toString().startsWith("Configured to connect to http://localhost:8005 using 'digest' authentication and username of 'admin'"), + "Unexpected toString content: " + testRestResult + ); + } +} diff --git a/src/test/java/com/marklogic/appdeployer/command/data/LoadDataTest.java b/src/test/java/com/marklogic/appdeployer/command/data/LoadDataTest.java index fd553c60..f44b5dca 100644 --- a/src/test/java/com/marklogic/appdeployer/command/data/LoadDataTest.java +++ b/src/test/java/com/marklogic/appdeployer/command/data/LoadDataTest.java @@ -20,6 +20,7 @@ import com.marklogic.appdeployer.command.restapis.DeployRestApiServersCommand; import com.marklogic.client.DatabaseClient; import com.marklogic.client.document.GenericDocumentManager; +import com.marklogic.client.ext.file.GenericFileLoader; import com.marklogic.client.io.DocumentMetadataHandle; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -27,7 +28,11 @@ import java.io.File; import java.util.Set; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; public class LoadDataTest extends AbstractAppDeployerTest { @@ -92,4 +97,18 @@ public void databaseNameIsSet() { assertEquals(appConfig.getAppServicesPort(), client.getPort()); client.release(); } + + /** + * Just verifies the config; we assume that ml-javaclient-util will work properly if cascade is set to true. + */ + @Test + void cascadeCollectionsAndPermissions() { + appConfig.setCascadePermissions(true); + appConfig.setCascadeCollections(true); + + GenericFileLoader loader = (GenericFileLoader) new LoadDataCommand().buildFileLoader(appConfig); + + assertTrue(loader.isCascadeCollections()); + assertTrue(loader.isCascadePermissions()); + } } diff --git a/src/test/java/com/marklogic/appdeployer/command/modules/DefaultsModulesLoaderFactoryTest.java b/src/test/java/com/marklogic/appdeployer/command/modules/DefaultsModulesLoaderFactoryTest.java index d971c3f4..b5672632 100644 --- a/src/test/java/com/marklogic/appdeployer/command/modules/DefaultsModulesLoaderFactoryTest.java +++ b/src/test/java/com/marklogic/appdeployer/command/modules/DefaultsModulesLoaderFactoryTest.java @@ -21,7 +21,9 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; public class DefaultsModulesLoaderFactoryTest extends AbstractAppDeployerTest { @@ -43,4 +45,20 @@ public void dontUseHost() { PropertiesModuleManager manager = (PropertiesModuleManager) loader.getModulesManager(); assertNull(manager.getHost()); } + + @Test + void cascadeCollectionsAndPermissions() { + DefaultModulesLoader loader = (DefaultModulesLoader) factory.newModulesLoader(appConfig); + + // Should default to false in the 4.x timeframe + assertFalse(loader.getAssetFileLoader().isCascadeCollections()); + assertFalse(loader.getAssetFileLoader().isCascadePermissions()); + + appConfig.setCascadeCollections(true); + appConfig.setCascadePermissions(true); + + loader = (DefaultModulesLoader) factory.newModulesLoader(appConfig); + assertTrue(loader.getAssetFileLoader().isCascadeCollections()); + assertTrue(loader.getAssetFileLoader().isCascadePermissions()); + } } diff --git a/src/test/java/com/marklogic/appdeployer/command/restapis/DeleteRestApiTest.java b/src/test/java/com/marklogic/appdeployer/command/restapis/DeleteRestApiTest.java index 18b2a802..13a33337 100644 --- a/src/test/java/com/marklogic/appdeployer/command/restapis/DeleteRestApiTest.java +++ b/src/test/java/com/marklogic/appdeployer/command/restapis/DeleteRestApiTest.java @@ -18,9 +18,13 @@ import com.marklogic.appdeployer.AbstractAppDeployerTest; import com.marklogic.appdeployer.ConfigDir; import com.marklogic.appdeployer.command.databases.DeployOtherDatabasesCommand; +import com.marklogic.mgmt.api.API; +import com.marklogic.mgmt.api.database.Database; +import com.marklogic.mgmt.api.server.Server; import com.marklogic.mgmt.resource.appservers.ServerManager; import com.marklogic.mgmt.resource.databases.DatabaseManager; import com.marklogic.mgmt.resource.restapis.RestApiManager; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.File; @@ -50,6 +54,28 @@ public void createAndDelete() { assertFalse(serverMgr.exists(SAMPLE_APP_NAME), "The REST API app server have been deleted"); } + @Test + @Disabled("This fails due to a server bug - BUG-60358") + void deleteWithFilesystemAsModulesDatabase() { + initializeAppDeployer(new DeployRestApiServersCommand(true)); + appDeployer.deploy(appConfig); + + API api = new API(manageClient); + + // Set the modules-database to the "filesystem", which will cause the DELETE to /v1/rest-apis/to fail due to + // the server bug. + Server server = new Server(api, appConfig.getRestServerName()); + server.setModulesDatabase("0"); + server.save(); + + try { + appDeployer.undeploy(appConfig); + } finally { + // Delete the modules-database to ensure it's not left around + new Database(api, appConfig.getModulesDatabaseName()).delete(); + } + } + @Test public void contentDatabaseCommandAndRestApiCommandConfiguredToDeleteContent() { DatabaseManager dbMgr = new DatabaseManager(manageClient); diff --git a/src/test/java/com/marklogic/appdeployer/command/schemas/LoadSchemasTest.java b/src/test/java/com/marklogic/appdeployer/command/schemas/LoadSchemasTest.java index ea3091b6..3041b927 100644 --- a/src/test/java/com/marklogic/appdeployer/command/schemas/LoadSchemasTest.java +++ b/src/test/java/com/marklogic/appdeployer/command/schemas/LoadSchemasTest.java @@ -20,6 +20,7 @@ import com.marklogic.appdeployer.command.databases.DeployOtherDatabasesCommand; import com.marklogic.client.DatabaseClient; import com.marklogic.client.document.GenericDocumentManager; +import com.marklogic.client.ext.schemasloader.impl.DefaultSchemasLoader; import com.marklogic.client.io.BytesHandle; import com.marklogic.client.io.DocumentMetadataHandle; import org.junit.jupiter.api.AfterEach; @@ -28,7 +29,11 @@ import java.io.File; import java.io.FileFilter; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; public class LoadSchemasTest extends AbstractAppDeployerTest { @@ -148,6 +153,9 @@ public void multipleSchemaPaths() { File projectDir = new File("src/test/resources/schemas-project"); initializeAppConfig(projectDir); + + // Turn off TDE validation, just to verify that the QBV still gets processed + appConfig.setTdeValidationEnabled(false); appConfig.getSchemaPaths().add(new File(projectDir, "src/main/more-schemas").getAbsolutePath()); initializeAppDeployer(new DeployOtherDatabasesCommand(1), new LoadSchemasCommand()); @@ -157,9 +165,30 @@ public void multipleSchemaPaths() { GenericDocumentManager docMgr = client.newDocumentManager(); assertNotNull(docMgr.exists("/tde/template1.json")); assertNotNull(docMgr.exists("/tde/template2.json")); + assertNotNull(docMgr.exists("/qbv/example.sjs.xml"), + "The QBV XML should have been generated, even though TDE validation is disabled."); + + assertTrue(docMgr.readMetadata("/tde/template1.json", + new DocumentMetadataHandle()).getCollections().contains("http://marklogic.com/xdmp/tde")); + assertTrue(docMgr.readMetadata("/tde/template2.json", + new DocumentMetadataHandle()).getCollections().contains("http://marklogic.com/xdmp/tde")); + assertTrue(docMgr.readMetadata("/qbv/example.sjs.xml", + new DocumentMetadataHandle()).getCollections().contains("http://marklogic.com/xdmp/qbv")); + } + + /** + * Just verifies the config; we assume that ml-javaclient-util will work properly if cascade is set to true. + */ + @Test + void cascadeCollectionsAndPermissions() { + appConfig.setCascadePermissions(true); + appConfig.setCascadeCollections(true); + + DefaultSchemasLoader loader = (DefaultSchemasLoader) new LoadSchemasCommand().buildSchemasLoader( + newCommandContext(), appConfig.newSchemasDatabaseClient(), null); - assertTrue(docMgr.readMetadata("/tde/template1.json", new DocumentMetadataHandle()).getCollections().contains("http://marklogic.com/xdmp/tde")); - assertTrue(docMgr.readMetadata("/tde/template2.json", new DocumentMetadataHandle()).getCollections().contains("http://marklogic.com/xdmp/tde")); + assertTrue(loader.isCascadeCollections()); + assertTrue(loader.isCascadePermissions()); } private Command newCommand() { diff --git a/src/test/java/com/marklogic/appdeployer/scaffold/GenerateScaffoldTest.java b/src/test/java/com/marklogic/appdeployer/scaffold/GenerateScaffoldTest.java index b7f01abb..0f6dbd21 100644 --- a/src/test/java/com/marklogic/appdeployer/scaffold/GenerateScaffoldTest.java +++ b/src/test/java/com/marklogic/appdeployer/scaffold/GenerateScaffoldTest.java @@ -24,65 +24,121 @@ import com.marklogic.mgmt.resource.databases.DatabaseManager; import com.marklogic.mgmt.resource.security.RoleManager; import com.marklogic.mgmt.resource.security.UserManager; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.File; +import java.io.IOException; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class GenerateScaffoldTest extends AbstractAppDeployerTest { - @Test - public void generateScaffoldAndThenDeploy() { - // Assume this is run out of the main directory, so default to "." and build out src/main etc. - String path = "src/test/resources/scaffold-test"; - File dir = new File(path); - dir.delete(); - dir.mkdirs(); - - ScaffoldGenerator sg = new ScaffoldGenerator(); - sg.generateScaffold(path, appConfig); + // Assume this is run out of the main directory, so default to "." and build out src/main etc. + final static String ROOT_PATH = "src/test/resources/scaffold-test"; + final static File dir = new File(ROOT_PATH); + final static File configDir = new File(dir, "src/main/ml-config"); + final static File modulesDir = new File(dir, "src/main/ml-modules"); - assertConfigFilesAreCreated(dir); - assertModulesFilesAreCreated(dir); + @BeforeEach + public void beforeEach() throws IOException { + if (dir.exists()) { + FileUtils.deleteDirectory(dir); + } + assertTrue(dir.mkdirs()); + } - // Now try deploying the app - appConfig.setConfigDir(new ConfigDir(new File(path, "src/main/ml-config"))); - appConfig.getModulePaths().clear(); - appConfig.getModulePaths().add(path + "/src/main/ml-modules"); + @Test + public void generateScaffoldWithDefaultsAndThenDeploy() { + ScaffoldGenerator.AppInputs appInputs = new ScaffoldGenerator.AppInputs(SAMPLE_APP_NAME, true, true); + new ScaffoldGenerator().generateScaffold(ROOT_PATH, appInputs); - initializeAppDeployer(new DeployRestApiServersCommand(), new DeployOtherDatabasesCommand(), - new DeployUsersCommand(), new DeployRolesCommand(), buildLoadModulesCommand()); - appDeployer.deploy(appConfig); + assertConfigFilesAreCreated( true); + assertModulesFilesAreCreated(true, true); + deployAppUsingAppConfig(); try { DatabaseManager dbMgr = new DatabaseManager(manageClient); - assertTrue(dbMgr.exists(appConfig.getContentDatabaseName())); - - assertTrue(new UserManager(manageClient).exists("sample-app-reader")); - assertTrue(new UserManager(manageClient).exists("sample-app-writer")); - assertTrue(new UserManager(manageClient).exists("sample-app-admin")); - assertTrue(new RoleManager(manageClient).exists("sample-app-nobody")); - assertTrue(new RoleManager(manageClient).exists("sample-app-reader")); - assertTrue(new RoleManager(manageClient).exists("sample-app-writer")); - assertTrue(new RoleManager(manageClient).exists("sample-app-internal")); - assertTrue(new RoleManager(manageClient).exists("sample-app-admin")); + assertTrue(dbMgr.exists(SAMPLE_APP_NAME + "-content")); + assertTrue(dbMgr.exists(SAMPLE_APP_NAME + "-schemas")); + assertSecurity(true); } finally { undeploySampleApp(); } } - private void assertConfigFilesAreCreated(File dir) { - File configDir = new File(dir, "src/main/ml-config"); + @Test + public void generateScaffoldWithChoicesSetToFalseAndThenDeploy() { + ScaffoldGenerator.AppInputs appInputs = new ScaffoldGenerator.AppInputs(SAMPLE_APP_NAME, false, false); + new ScaffoldGenerator().generateScaffold(ROOT_PATH, appInputs); + + assertConfigFilesAreCreated(false); + assertModulesFilesAreCreated(false, false); + + deployAppUsingAppConfig(); + try { + DatabaseManager dbMgr = new DatabaseManager(manageClient); + assertTrue(dbMgr.exists(appConfig.getContentDatabaseName())); + assertSecurity(false); + } finally { + undeploySampleApp(); + } + } + + @Test + public void generateScaffoldWithDatabaseNoSecurityAndThenDeploy() { + ScaffoldGenerator.AppInputs appInputs = new ScaffoldGenerator.AppInputs(SAMPLE_APP_NAME, true, false); + new ScaffoldGenerator().generateScaffold(ROOT_PATH, appInputs); + + assertConfigFilesAreCreated(true); + assertModulesFilesAreCreated(true, true); + + deployAppUsingAppConfig(); + try { + DatabaseManager dbMgr = new DatabaseManager(manageClient); + assertTrue(dbMgr.exists(SAMPLE_APP_NAME + "-content")); + assertTrue(dbMgr.exists(SAMPLE_APP_NAME + "-schemas")); + assertSecurity(false); + } finally { + undeploySampleApp(); + } + } + + private void deployAppUsingAppConfig() { + appConfig.setConfigDir(new ConfigDir(new File(ROOT_PATH, "src/main/ml-config"))); + appConfig.getModulePaths().clear(); + appConfig.getModulePaths().add(ROOT_PATH + "/src/main/ml-modules"); + initializeAppDeployer(new DeployRestApiServersCommand(), new DeployOtherDatabasesCommand(), + new DeployUsersCommand(), new DeployRolesCommand(), buildLoadModulesCommand()); + appDeployer.deploy(appConfig); + } + + private void assertConfigFilesAreCreated(Boolean restApiExist) { assertTrue(configDir.exists()); - assertTrue(new File(configDir, "rest-api.json").exists()); - assertTrue(new File(configDir, "databases/content-database.json").exists()); + assertEquals(restApiExist, new File(configDir, "rest-api.json").exists()); + assertTrue(new File(configDir, "databases/content-database.json").exists()); + assertTrue(new File(configDir, "databases/schemas-database.json").exists()); } - private void assertModulesFilesAreCreated(File dir) { - File modulesDir = new File(dir, "src/main/ml-modules"); + private void assertModulesFilesAreCreated(Boolean restPropertiesExist, Boolean sampleOptionsExist) { assertTrue(modulesDir.exists()); - assertTrue(new File(modulesDir, "rest-properties.json").exists()); - assertTrue(new File(modulesDir, "options/sample-app-options.xml").exists()); + assertEquals(restPropertiesExist, new File(modulesDir, "rest-properties.json").exists()); + assertEquals(sampleOptionsExist, new File(modulesDir, "options/sample-app-options.xml").exists()); } + + private void assertSecurity(Boolean shouldExist) { + assertEquals(shouldExist, new UserManager(manageClient).exists("sample-app-reader")); + assertEquals(shouldExist, new UserManager(manageClient).exists("sample-app-writer")); + assertEquals(shouldExist, new UserManager(manageClient).exists("sample-app-admin")); + assertEquals(shouldExist, new RoleManager(manageClient).exists("sample-app-nobody")); + assertEquals(shouldExist, new RoleManager(manageClient).exists("sample-app-reader")); + assertEquals(shouldExist, new RoleManager(manageClient).exists("sample-app-writer")); + assertEquals(shouldExist, new RoleManager(manageClient).exists("sample-app-internal")); + assertEquals(shouldExist, new RoleManager(manageClient).exists("sample-app-admin")); + if (shouldExist) { + assertTrue(new RoleManager(manageClient).getPropertiesAsXmlString("sample-app-reader").contains("http://marklogic.com/xdmp/privileges/rest-reader")); + assertTrue(new RoleManager(manageClient).getPropertiesAsXmlString("sample-app-writer").contains("http://marklogic.com/xdmp/privileges/rest-writer")); + } + } } diff --git a/src/test/java/com/marklogic/rest/util/RestConfigTest.java b/src/test/java/com/marklogic/rest/util/RestConfigTest.java index 24a7a006..c0a9acf4 100644 --- a/src/test/java/com/marklogic/rest/util/RestConfigTest.java +++ b/src/test/java/com/marklogic/rest/util/RestConfigTest.java @@ -72,4 +72,13 @@ void buildUriWithBasePath() { config.setBasePath("/secure"); assertEquals("https://somehost:8002/secure/target/path", config.buildUri(targetPath).toString()); } + + @Test + void verifyToString() { + RestConfig config = new RestConfig("somewhere", 8008, "some", "user"); + assertEquals("http://somewhere:8008", config.toString()); + + config.setScheme("https"); + assertEquals("https://somewhere:8008", config.toString()); + } } diff --git a/src/test/java/com/marklogic/rest/util/RestTemplateUtilTest.java b/src/test/java/com/marklogic/rest/util/RestTemplateUtilTest.java index 3aa82e91..238681f2 100644 --- a/src/test/java/com/marklogic/rest/util/RestTemplateUtilTest.java +++ b/src/test/java/com/marklogic/rest/util/RestTemplateUtilTest.java @@ -143,7 +143,7 @@ void noUsername() { RuntimeException ex = assertThrows(RuntimeException.class, () -> RestTemplateUtil.newRestTemplate(manageConfig)); - assertEquals("Unable to connect to the MarkLogic app server at http://localhosthost:8002; cause: username must be of type String", + assertEquals("Unable to connect to the MarkLogic app server at http://localhost:8002; cause: username must be of type String", ex.getMessage(), "As of 4.5.0, since auth strategies other than basic/digest are now supported, the error message is expected " + "to identify which MarkLogic app server is being accessed but not any authentication details. This is " + diff --git a/src/test/resources/schemas-project/src/main/more-schemas/qbv/example.sjs b/src/test/resources/schemas-project/src/main/more-schemas/qbv/example.sjs new file mode 100644 index 00000000..b28b2a3d --- /dev/null +++ b/src/test/resources/schemas-project/src/main/more-schemas/qbv/example.sjs @@ -0,0 +1,3 @@ +'use strict'; +const op = require('/MarkLogic/optic'); +op.fromView('Example2', 'default').generateView('qbv', 'example')