mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Test code refactoring for finer-grained failure reporting.
Also separation of distinct config tests into distinct classes. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@12073 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -24,7 +24,6 @@
|
||||
*/
|
||||
package org.alfresco.web.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -36,7 +35,6 @@ import java.util.Set;
|
||||
import org.alfresco.config.Config;
|
||||
import org.alfresco.config.ConfigElement;
|
||||
import org.alfresco.config.ConfigException;
|
||||
import org.alfresco.config.source.FileConfigSource;
|
||||
import org.alfresco.config.xml.XMLConfigService;
|
||||
import org.alfresco.util.BaseTest;
|
||||
import org.alfresco.web.config.ActionsConfigElement.ActionDefinition;
|
||||
@@ -57,7 +55,7 @@ import org.alfresco.web.config.WizardsConfigElement.WizardConfig;
|
||||
* JUnit tests to exercise the capabilities added to the web client config
|
||||
* service
|
||||
*
|
||||
* @author gavinc, neil
|
||||
* @author gavinc
|
||||
*/
|
||||
public class WebClientConfigTest extends BaseTest
|
||||
{
|
||||
@@ -904,442 +902,4 @@ public class WebClientConfigTest extends BaseTest
|
||||
assertEquals("number of items in new_group group", 1, actions.size());
|
||||
assertEquals("action", "custom_action", actions.get(0));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testDefaultControlsConfig()
|
||||
{
|
||||
XMLConfigService svc = initXMLConfigService("test-config-forms.xml");
|
||||
|
||||
// get hold of the default-controls config from the global section
|
||||
Config globalConfig = svc.getGlobalConfig();
|
||||
ConfigElement globalDefaultControls = globalConfig
|
||||
.getConfigElement("default-controls");
|
||||
assertNotNull("global default-controls element should not be null",
|
||||
globalDefaultControls);
|
||||
assertTrue(
|
||||
"config element should be an instance of DefaultControlsConfigElement",
|
||||
(globalDefaultControls instanceof DefaultControlsConfigElement));
|
||||
|
||||
// Test that the default-control types are read from the config file
|
||||
Map<String, String> expectedDataMappings = new HashMap<String, String>();
|
||||
expectedDataMappings.put("d:text",
|
||||
"org/alfresco/forms/controls/textfield.ftl");
|
||||
expectedDataMappings.put("d:boolean",
|
||||
"org/alfresco/forms/controls/checkbox.ftl");
|
||||
expectedDataMappings.put("association",
|
||||
"org/alfresco/forms/controls/association-picker.ftl");
|
||||
expectedDataMappings.put("abc", "org/alfresco/abc.ftl");
|
||||
|
||||
DefaultControlsConfigElement dcConfigElement = (DefaultControlsConfigElement) globalDefaultControls;
|
||||
Set<String> actualNames = dcConfigElement.getNames();
|
||||
assertEquals("Incorrect name count, expected "
|
||||
+ expectedDataMappings.size(), expectedDataMappings.size(),
|
||||
actualNames.size());
|
||||
|
||||
// Ugly hack to get around JUnit 3.8.1 not having
|
||||
// assertEquals(Collection, Collection)
|
||||
for (String nextName : expectedDataMappings.keySet())
|
||||
{
|
||||
assertTrue("actualNames was missing " + nextName, actualNames
|
||||
.contains(nextName));
|
||||
}
|
||||
for (String nextName : actualNames)
|
||||
{
|
||||
assertTrue("expectedDataMappings was missing " + nextName,
|
||||
expectedDataMappings.keySet().contains(nextName));
|
||||
}
|
||||
|
||||
// Test that the datatypes map to the expected template.
|
||||
for (String nextKey : expectedDataMappings.keySet())
|
||||
{
|
||||
String nextExpectedValue = expectedDataMappings.get(nextKey);
|
||||
String nextActualValue = dcConfigElement.getTemplateFor(nextKey);
|
||||
assertTrue("Incorrect template for " + nextKey + ": "
|
||||
+ nextActualValue, nextExpectedValue
|
||||
.equals(nextActualValue));
|
||||
}
|
||||
|
||||
Map<String, List<ControlParam>> expectedControlParams = new HashMap<String, List<ControlParam>>();
|
||||
|
||||
List<ControlParam> textParams = new ArrayList<ControlParam>();
|
||||
textParams.add(new ControlParam("size", "50"));
|
||||
|
||||
List<ControlParam> abcParams = new ArrayList<ControlParam>();
|
||||
abcParams.add(new ControlParam("a", "1"));
|
||||
abcParams.add(new ControlParam("b", "Hello"));
|
||||
abcParams.add(new ControlParam("c", "For ever and ever."));
|
||||
abcParams.add(new ControlParam("d", ""));
|
||||
|
||||
expectedControlParams.put("d:text", textParams);
|
||||
expectedControlParams.put("d:boolean", Collections.EMPTY_LIST);
|
||||
expectedControlParams.put("association", Collections.EMPTY_LIST);
|
||||
expectedControlParams.put("abc", abcParams);
|
||||
|
||||
for (String name : expectedControlParams.keySet())
|
||||
{
|
||||
List<ControlParam> actualControlParams = dcConfigElement
|
||||
.getControlParamsFor(name);
|
||||
assertEquals("Incorrect params for " + name, expectedControlParams
|
||||
.get(name), actualControlParams);
|
||||
}
|
||||
|
||||
// test that a call to the generic getChildren call throws an error
|
||||
try
|
||||
{
|
||||
dcConfigElement.getChildren();
|
||||
fail("getChildren() did not throw an exception");
|
||||
} catch (ConfigException ce)
|
||||
{
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testDefaultControlsOverride()
|
||||
{
|
||||
XMLConfigService svc = initXMLConfigService("test-config-forms.xml",
|
||||
"test-config-forms-override.xml");
|
||||
|
||||
// get hold of the default-controls config from the global section
|
||||
Config globalConfig = svc.getGlobalConfig();
|
||||
ConfigElement globalDefaultControls = globalConfig
|
||||
.getConfigElement("default-controls");
|
||||
assertNotNull("global default-controls element should not be null",
|
||||
globalDefaultControls);
|
||||
assertTrue(
|
||||
"config element should be an instance of DefaultControlsConfigElement",
|
||||
(globalDefaultControls instanceof DefaultControlsConfigElement));
|
||||
DefaultControlsConfigElement dcCE = (DefaultControlsConfigElement) globalDefaultControls;
|
||||
|
||||
assertTrue("New template is missing.", dcCE.getNames().contains("xyz"));
|
||||
assertEquals("Expected template incorrect.", "org/alfresco/xyz.ftl",
|
||||
dcCE.getTemplateFor("xyz"));
|
||||
|
||||
ControlParam expectedNewControlParam = new ControlParam("c", "Never.");
|
||||
assertTrue("New control-param missing.", dcCE
|
||||
.getControlParamsFor("abc").contains(expectedNewControlParam));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the combination of a DefaultControlsConfigElement with another that
|
||||
* contains additional data.
|
||||
*/
|
||||
public void testDefaultControlsCombine_Addition()
|
||||
{
|
||||
DefaultControlsConfigElement basicElement = new DefaultControlsConfigElement();
|
||||
basicElement.addDataMapping("text", "path/textbox.ftl", null);
|
||||
|
||||
// This element is the same as the above, but adds a control-param.
|
||||
DefaultControlsConfigElement parameterisedElement = new DefaultControlsConfigElement();
|
||||
List<ControlParam> testParams = new ArrayList<ControlParam>();
|
||||
testParams.add(new ControlParam("A", "1"));
|
||||
parameterisedElement.addDataMapping("text", "path/textbox.ftl",
|
||||
testParams);
|
||||
|
||||
ConfigElement combinedElem = basicElement.combine(parameterisedElement);
|
||||
assertEquals("Combined elem incorrect.", parameterisedElement,
|
||||
combinedElem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the combination of a DefaultControlsConfigElement with another that
|
||||
* contains modified data.
|
||||
*/
|
||||
public void testDefaultControlsCombine_Modification()
|
||||
{
|
||||
DefaultControlsConfigElement initialElement = new DefaultControlsConfigElement();
|
||||
List<ControlParam> testParams = new ArrayList<ControlParam>();
|
||||
testParams.add(new ControlParam("A", "1"));
|
||||
initialElement.addDataMapping("text", "path/textbox.ftl", testParams);
|
||||
|
||||
// This element is the same as the above, but modifies the
|
||||
// control-param.
|
||||
DefaultControlsConfigElement modifiedElement = new DefaultControlsConfigElement();
|
||||
List<ControlParam> modifiedTestParams = new ArrayList<ControlParam>();
|
||||
modifiedTestParams.add(new ControlParam("A", "5"));
|
||||
modifiedElement.addDataMapping("text", "path/textbox.ftl",
|
||||
modifiedTestParams);
|
||||
|
||||
ConfigElement combinedElem = initialElement.combine(modifiedElement);
|
||||
assertEquals("Combined elem incorrect.", modifiedElement, combinedElem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the combination of a DefaultControlsConfigElement with another that
|
||||
* contains deleted data. TODO Do we actually need to support this type of
|
||||
* customisation?
|
||||
*/
|
||||
public void testDefaultControlsCombine_Deletion()
|
||||
{
|
||||
DefaultControlsConfigElement initialElement = new DefaultControlsConfigElement();
|
||||
List<ControlParam> testParams = new ArrayList<ControlParam>();
|
||||
testParams.add(new ControlParam("A", "1"));
|
||||
initialElement.addDataMapping("text", "path/textbox.ftl", testParams);
|
||||
|
||||
// This element is the same as the above, but modifies the
|
||||
// control-param.
|
||||
DefaultControlsConfigElement modifiedElement = new DefaultControlsConfigElement();
|
||||
modifiedElement.addDataMapping("text", "path/textbox.ftl", null);
|
||||
|
||||
ConfigElement combinedElem = initialElement.combine(modifiedElement);
|
||||
assertEquals("Combined elem incorrect.", modifiedElement, combinedElem);
|
||||
}
|
||||
|
||||
public void testConstraintHandlersConfig()
|
||||
{
|
||||
XMLConfigService svc = initXMLConfigService("test-config-forms.xml");
|
||||
|
||||
// get hold of the constraint-handlers config from the global section
|
||||
Config globalConfig = svc.getGlobalConfig();
|
||||
ConfigElement globalConstraintHandlers = globalConfig
|
||||
.getConfigElement("constraint-handlers");
|
||||
assertNotNull("global constraint-handlers element should not be null",
|
||||
globalConstraintHandlers);
|
||||
assertTrue(
|
||||
"config element should be an instance of ConstraintHandlersConfigElement",
|
||||
(globalConstraintHandlers instanceof ConstraintHandlersConfigElement));
|
||||
|
||||
// Test that the constraint-handlers' constraints are read from the
|
||||
// config file
|
||||
Map<String, String> expectedValidationHandlers = new HashMap<String, String>();
|
||||
expectedValidationHandlers.put("REGEX",
|
||||
"Alfresco.forms.validation.regexMatch");
|
||||
expectedValidationHandlers.put("NUMERIC",
|
||||
"Alfresco.forms.validation.numericMatch");
|
||||
|
||||
ConstraintHandlersConfigElement chConfigElement = (ConstraintHandlersConfigElement) globalConstraintHandlers;
|
||||
List<String> actualTypes = chConfigElement.getConstraintTypes();
|
||||
assertEquals("Incorrect type count.",
|
||||
expectedValidationHandlers.size(), actualTypes.size());
|
||||
|
||||
// Ugly hack to get around JUnit 3.8.1 not having
|
||||
// assertEquals(Collection, Collection)
|
||||
for (String nextType : expectedValidationHandlers.keySet())
|
||||
{
|
||||
assertTrue("actualTypes was missing " + nextType, actualTypes
|
||||
.contains(nextType));
|
||||
}
|
||||
for (String nextType : actualTypes)
|
||||
{
|
||||
assertTrue("expectedValidationHandlers missing " + nextType,
|
||||
expectedValidationHandlers.keySet().contains(nextType));
|
||||
}
|
||||
|
||||
// Test that the types map to the expected validation handler.
|
||||
for (String nextKey : expectedValidationHandlers.keySet())
|
||||
{
|
||||
String nextExpectedValue = expectedValidationHandlers.get(nextKey);
|
||||
String nextActualValue = chConfigElement
|
||||
.getValidationHandlerFor(nextKey);
|
||||
assertTrue("Incorrect handler for " + nextKey + ": "
|
||||
+ nextActualValue, nextExpectedValue
|
||||
.equals(nextActualValue));
|
||||
}
|
||||
|
||||
// Test that the constraint-handlers' messages are read from the config
|
||||
// file
|
||||
Map<String, String> expectedMessages = new HashMap<String, String>();
|
||||
expectedMessages.put("REGEX", null);
|
||||
expectedMessages.put("NUMERIC", "Test Message");
|
||||
|
||||
// Test that the types map to the expected message.
|
||||
for (String nextKey : expectedValidationHandlers.keySet())
|
||||
{
|
||||
String nextExpectedValue = expectedMessages.get(nextKey);
|
||||
String nextActualValue = chConfigElement.getMessageFor(nextKey);
|
||||
assertEquals("Incorrect message for " + nextKey + ".",
|
||||
nextExpectedValue, nextActualValue);
|
||||
}
|
||||
|
||||
// Test that the constraint-handlers' message-ids are read from the config
|
||||
// file
|
||||
Map<String, String> expectedMessageIDs = new HashMap<String, String>();
|
||||
expectedMessageIDs.put("REGEX", null);
|
||||
expectedMessageIDs.put("NUMERIC", "regex_error");
|
||||
|
||||
// Test that the types map to the expected message-id.
|
||||
for (String nextKey : expectedValidationHandlers.keySet())
|
||||
{
|
||||
String nextExpectedValue = expectedMessageIDs.get(nextKey);
|
||||
String nextActualValue = chConfigElement.getMessageIdFor(nextKey);
|
||||
assertEquals("Incorrect message-id for " + nextKey + ".",
|
||||
nextExpectedValue, nextActualValue);
|
||||
}
|
||||
|
||||
// test that a call to the generic getChildren call throws an error
|
||||
try
|
||||
{
|
||||
chConfigElement.getChildren();
|
||||
fail("getChildren() did not throw an exception");
|
||||
} catch (ConfigException ce)
|
||||
{
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testConstraintHandlersOverride()
|
||||
{
|
||||
XMLConfigService svc = initXMLConfigService("test-config-forms.xml",
|
||||
"test-config-forms-override.xml");
|
||||
|
||||
// get hold of the constraint-handlers config from the global section
|
||||
Config globalConfig = svc.getGlobalConfig();
|
||||
ConfigElement globalConstraintHandlers = globalConfig
|
||||
.getConfigElement("constraint-handlers");
|
||||
assertNotNull("global constraint-handlers element should not be null",
|
||||
globalConstraintHandlers);
|
||||
assertTrue(
|
||||
"config element should be an instance of ConstraintHandlersConfigElement",
|
||||
(globalConstraintHandlers instanceof ConstraintHandlersConfigElement));
|
||||
ConstraintHandlersConfigElement chCE = (ConstraintHandlersConfigElement) globalConstraintHandlers;
|
||||
|
||||
assertTrue("New type is missing.", chCE.getConstraintTypes().contains(
|
||||
"RANGE"));
|
||||
assertEquals("Expected handler incorrect.",
|
||||
"Alfresco.forms.validation.rangeMatch", chCE
|
||||
.getValidationHandlerFor("RANGE"));
|
||||
|
||||
assertEquals("Modified message is wrong.", "Overridden Message", chCE
|
||||
.getMessageFor("NUMERIC"));
|
||||
}
|
||||
|
||||
public void testFormConfig()
|
||||
{
|
||||
XMLConfigService svc = initXMLConfigService("test-config-forms.xml");
|
||||
|
||||
Config contentConfig = svc.getConfig("content");
|
||||
ConfigElement confElement = contentConfig.getConfigElement("form");
|
||||
assertNotNull("confElement was null.", confElement);
|
||||
assertTrue("confElement should be instanceof FormConfigElement.", confElement
|
||||
instanceof FormConfigElement);
|
||||
FormConfigElement formConfigElement = (FormConfigElement)confElement;
|
||||
|
||||
assertEquals("Submission URL was incorrect.", "submission/url",
|
||||
formConfigElement.getSubmissionURL());
|
||||
|
||||
List<StringPair> expectedModelOverrideProperties = new ArrayList<StringPair>();
|
||||
expectedModelOverrideProperties.add(new StringPair("fields.title.mandatory", "true"));
|
||||
assertEquals("Expected property missing.", expectedModelOverrideProperties,
|
||||
formConfigElement.getModelOverrideProperties());
|
||||
|
||||
// Get the form templates. Testing the mode and role combinations.
|
||||
// For this config xml, there are no templates available to a user without a role.
|
||||
assertNull("Incorrect template.", formConfigElement.getFormTemplate(Mode.CREATE, null));
|
||||
assertNull("Incorrect template.", formConfigElement.getFormTemplate(Mode.EDIT, null));
|
||||
assertNull("Incorrect template.", formConfigElement.getFormTemplate(Mode.VIEW, null));
|
||||
assertNull("Incorrect template.", formConfigElement.getFormTemplate(Mode.CREATE, Collections.EMPTY_LIST));
|
||||
assertNull("Incorrect template.", formConfigElement.getFormTemplate(Mode.EDIT, Collections.EMPTY_LIST));
|
||||
assertNull("Incorrect template.", formConfigElement.getFormTemplate(Mode.VIEW, Collections.EMPTY_LIST));
|
||||
|
||||
List<String> roles = new ArrayList<String>();
|
||||
roles.add("Consumer");
|
||||
roles.add("Manager");
|
||||
assertEquals("Incorrect template.", "/path/create/template", formConfigElement.getFormTemplate(Mode.CREATE, roles));
|
||||
assertEquals("Incorrect template.", "/path/edit/template/manager", formConfigElement.getFormTemplate(Mode.EDIT, roles));
|
||||
assertEquals("Incorrect template.", "/path/view/template", formConfigElement.getFormTemplate(Mode.VIEW, roles));
|
||||
|
||||
|
||||
// Field visibility checks.
|
||||
assertTrue("Field should be visible.", formConfigElement.isFieldVisible("name", Mode.CREATE));
|
||||
assertTrue("Field should be visible.", formConfigElement.isFieldVisible("title", Mode.CREATE));
|
||||
assertTrue("Field should be visible.", formConfigElement.isFieldVisible("quota", Mode.CREATE));
|
||||
assertFalse("Field should be invisible.", formConfigElement.isFieldVisible("rubbish", Mode.CREATE));
|
||||
|
||||
assertTrue("Field should be visible.", formConfigElement.isFieldVisible("name", Mode.EDIT));
|
||||
assertFalse("Field should be invisible.", formConfigElement.isFieldVisible("title", Mode.EDIT));
|
||||
assertFalse("Field should be invisible.", formConfigElement.isFieldVisible("quota", Mode.EDIT));
|
||||
assertFalse("Field should be invisible.", formConfigElement.isFieldVisible("rubbish", Mode.EDIT));
|
||||
|
||||
assertTrue("Field should be visible.", formConfigElement.isFieldVisible("name", Mode.VIEW));
|
||||
assertTrue("Field should be visible.", formConfigElement.isFieldVisible("title", Mode.VIEW));
|
||||
assertTrue("Field should be visible.", formConfigElement.isFieldVisible("quota", Mode.VIEW));
|
||||
assertFalse("Field should be invisible.", formConfigElement.isFieldVisible("rubbish", Mode.VIEW));
|
||||
|
||||
// Set checks
|
||||
List<String> expectedSetIds = new ArrayList<String>();
|
||||
expectedSetIds.add("details");
|
||||
expectedSetIds.add("user");
|
||||
assertEquals("Set IDs were wrong.", expectedSetIds, formConfigElement.getSetIDs());
|
||||
|
||||
Map<String, FormSet> sets = formConfigElement.getSets();
|
||||
assertEquals("Set parent was wrong.", "details", sets.get("user").getParentId());
|
||||
assertEquals("Set parent was wrong.", null, sets.get("details").getParentId());
|
||||
|
||||
assertEquals("Set parent was wrong.", "fieldset", sets.get("details").getAppearance());
|
||||
assertEquals("Set parent was wrong.", "panel", sets.get("user").getAppearance());
|
||||
|
||||
// Field checks
|
||||
Map<String, FormField> fields = formConfigElement.getFields();
|
||||
assertEquals("Wrong number of Fields.", 4, fields.size());
|
||||
|
||||
FormField usernameField = fields.get("username");
|
||||
assertNotNull("usernameField was null.", usernameField);
|
||||
assertTrue("Missing attribute.", usernameField.getAttributes().containsKey("set"));
|
||||
assertEquals("Incorrect attribute.", "user", usernameField.getAttributes().get("set"));
|
||||
assertNull("username field's template should be null.", usernameField.getTemplate());
|
||||
|
||||
FormField nameField = fields.get("name");
|
||||
String nameTemplate = nameField.getTemplate();
|
||||
assertNotNull("name field had null template", nameTemplate);
|
||||
assertEquals("name field had incorrect template.", "alfresco/extension/formcontrols/my-name.ftl", nameTemplate);
|
||||
|
||||
List<StringPair> controlParams = nameField.getControlParams();
|
||||
assertNotNull("name field should have control params.", controlParams);
|
||||
assertEquals("name field has incorrect number of control params.", 1, controlParams.size());
|
||||
|
||||
assertEquals("Control param has wrong name.", "foo", controlParams.get(0).getName());
|
||||
assertEquals("Control param has wrong value.", "bar", controlParams.get(0).getValue());
|
||||
|
||||
assertEquals("name field had incorrect type.", "REGEX", nameField.getConstraintType());
|
||||
assertEquals("name field had incorrect message.",
|
||||
"The name can not contain the character '{0}'", nameField.getConstraintMessage());
|
||||
assertEquals("name field had incorrect message-id.", "field_error_name",
|
||||
nameField.getConstraintMessageId());
|
||||
|
||||
// test that a call to the generic getChildren call throws an error
|
||||
try
|
||||
{
|
||||
formConfigElement.getChildren();
|
||||
fail("getChildren() did not throw an exception.");
|
||||
} catch (ConfigException expectedException)
|
||||
{
|
||||
// intentionally empty
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private XMLConfigService initXMLConfigService(String xmlConfigFile)
|
||||
{
|
||||
String fullFileName = getResourcesDir() + xmlConfigFile;
|
||||
assertFileIsValid(fullFileName);
|
||||
|
||||
XMLConfigService svc = new XMLConfigService(new FileConfigSource(
|
||||
fullFileName));
|
||||
svc.initConfig();
|
||||
return svc;
|
||||
}
|
||||
|
||||
private XMLConfigService initXMLConfigService(String xmlConfigFile,
|
||||
String overridingXmlConfigFile)
|
||||
{
|
||||
String mainConfigFile = getResourcesDir() + xmlConfigFile;
|
||||
String overridingConfigFile = getResourcesDir()
|
||||
+ overridingXmlConfigFile;
|
||||
assertFileIsValid(mainConfigFile);
|
||||
assertFileIsValid(overridingConfigFile);
|
||||
|
||||
List<String> configFiles = new ArrayList<String>();
|
||||
configFiles.add(mainConfigFile);
|
||||
configFiles.add(overridingConfigFile);
|
||||
XMLConfigService svc = new XMLConfigService(new FileConfigSource(
|
||||
configFiles));
|
||||
svc.initConfig();
|
||||
return svc;
|
||||
}
|
||||
|
||||
private void assertFileIsValid(String fullFileName)
|
||||
{
|
||||
File f = new File(fullFileName);
|
||||
assertTrue("Required file missing: " + fullFileName, f.exists());
|
||||
assertTrue("Required file not readable: " + fullFileName, f.canRead());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user