FakFtpServerIntegrationTest (first phase); homeDirectory support

git-svn-id: svn://svn.code.sf.net/p/mockftpserver/code@64 531de8e6-9941-0410-b38b-9a92acbe0330
diff --git a/MockFtpServer/src/main/groovy/org/mockftpserver/fake/command/AbstractFakeCommandHandler.groovy b/MockFtpServer/src/main/groovy/org/mockftpserver/fake/command/AbstractFakeCommandHandler.groovy
index 99aa693..322b614 100644
--- a/MockFtpServer/src/main/groovy/org/mockftpserver/fake/command/AbstractFakeCommandHandler.groovy
+++ b/MockFtpServer/src/main/groovy/org/mockftpserver/fake/command/AbstractFakeCommandHandler.groovy
@@ -32,6 +32,8 @@
 import org.mockftpserver.fake.filesystem.FileSystemException
 import org.mockftpserver.fake.filesystem.InvalidFilenameException
 import org.mockftpserver.fake.filesystem.NewFileOperationException
+import org.mockftpserver.fake.user.UserAccount
+
 
 /**
  * Abstract superclass for CommandHandler classes for the "Fake" server.
@@ -46,7 +48,7 @@
     ServerConfiguration serverConfiguration
 
     /**
-     * Reply code sent back when a FileSystemException is caught by the  {@link #handleCommand(Command, Session)}
+     * Reply code sent back when a FileSystemException is caught by the   {@link #handleCommand(Command, Session)}
      * This defaults to ReplyCodes.EXISTING_FILE_ERROR (550). 
      */
     int replyCodeForFileSystemException = ReplyCodes.EXISTING_FILE_ERROR
@@ -105,6 +107,37 @@
      * Send a reply for this command on the control connection.
      *
      * The reply code is designated by the <code>replyCode</code> property, and the reply text
+     * is retrieved from the <code>replyText</code> ResourceBundle, using the specified messageKey.
+     *
+     * @param session - the Session
+     * @param replyCode - the reply code
+     * @param messageKey - the resource bundle key for the reply text
+     * @param args - the optional message arguments; defaults to []
+     *
+     * @throws AssertionError - if session is null
+     *
+     * @see MessageFormat
+     */
+    protected void sendReply(Session session, int replyCode, String messageKey, List args = []) {
+        assert session
+        assertValidReplyCode(replyCode);
+
+        String key = Integer.toString(replyCode);
+        String text = getTextForKey(messageKey)
+
+        String replyText = (args) ? MessageFormat.format(text, args as Object[]) : text;
+
+        String replyTextToLog = (replyText == null) ? "" : " " + replyText;
+        // TODO change to LOG.debug()
+        def argsToLog = (args) ? " args=$args" : ""
+        LOG.info("Sending reply [" + replyCode + replyTextToLog + "]" + argsToLog);
+        session.sendReply(replyCode, replyText);
+    }
+
+    /**
+     * Send a reply for this command on the control connection.
+     *
+     * The reply code is designated by the <code>replyCode</code> property, and the reply text
      * is retrieved from the <code>replyText</code> ResourceBundle, using the reply code as the key.
      *
      * @param session - the Session
@@ -115,20 +148,8 @@
      *
      * @see MessageFormat
      */
-    protected void sendReply(Session session, int replyCode, args = []) {
-        assert session
-        assertValidReplyCode(replyCode);
-
-        String key = Integer.toString(replyCode);
-        String text = getTextForReplyCode(replyCode)
-
-        String replyText = (args) ? MessageFormat.format(text, args as Object[]) : text;
-
-        String replyTextToLog = (replyText == null) ? "" : " " + replyText;
-        // TODO change to LOG.debug()
-        def argsToLog = (args) ? " args=$args" : ""
-        LOG.info("Sending reply [" + replyCode + replyTextToLog + "]" + argsToLog);
-        session.sendReply(replyCode, replyText);
+    protected void sendReply(Session session, int replyCode, List args = []) {
+        sendReply(session, replyCode, replyCode.toString(), args)
     }
 
     /**
@@ -238,15 +259,58 @@
         "\n"
     }
 
-    private String getTextForReplyCode(int replyCode) {
+    private String getTextForKey(key) {
         try {
-            return serverConfiguration.replyTextBundle.getString(Integer.toString(replyCode))
+            return serverConfiguration.replyTextBundle.getString(key.toString())
         }
         catch (MissingResourceException e) {
             // No reply text is mapped for the specified key
-            LOG.warn("No reply text defined for reply code [${replyCode as String}]");
+            LOG.warn("No reply text defined for key [${key.toString()}]");
             return null;
         }
     }
 
+    // -------------------------------------------------------------------------
+    // Login Support (used by USER and PASS commands)
+    // -------------------------------------------------------------------------
+
+    /**
+     * Validate the UserAccount for the specified username. If valid, return true. If the UserAccount does
+     * not exist or is invalid, log an error message, send back a reply code of 530 with an appropriate
+     * error message, and return false. A UserAccount is considered invalid if the homeDirectory property
+     * is not set or is set to a non-existent directory.
+     * @param username - the username
+     * @param session - the session; used to send back an error reply if necessary
+     * @return true only if the UserAccount for the named user is valid
+     */
+    protected boolean validateUserAccount(String username, Session session) {
+        def userAccount = serverConfiguration.getUserAccount(username)
+        if (userAccount == null || !userAccount.valid) {
+            LOG.error("UserAccount missing or not valid for username [$username]: $userAccount")
+            sendReply(session, ReplyCodes.USER_ACCOUNT_NOT_VALID, "userAccountNotValid", [username])
+            return false
+        }
+
+        def home = userAccount.homeDirectory
+        if (!getFileSystem().isDirectory(home)) {
+            LOG.error("Home directory configured for username [$username] is not valid: $home")
+            sendReply(session, ReplyCodes.USER_ACCOUNT_NOT_VALID, "homeDirectoryNotValid", [username, home])
+            return false
+        }
+
+        return true
+    }
+
+    /**
+     * Log in the specified user for the current session. Send back a reply of 230 and set the UserAccount
+     * and current directory (homeDirectory) in the session
+     * @param userAccount - the userAccount for the user to be logged in
+     * @param session - the session
+     */
+    protected void login(UserAccount userAccount, Session session) {
+        sendReply(session, ReplyCodes.PASS_OK)
+        session.setAttribute(SessionKeys.USER_ACCOUNT, userAccount)
+        session.setAttribute(SessionKeys.CURRENT_DIRECTORY, userAccount.homeDirectory)
+    }
+
 }
\ No newline at end of file
diff --git a/MockFtpServer/src/main/groovy/org/mockftpserver/fake/command/PassCommandHandler.groovy b/MockFtpServer/src/main/groovy/org/mockftpserver/fake/command/PassCommandHandler.groovy
index 320e4d0..0d6f60a 100644
--- a/MockFtpServer/src/main/groovy/org/mockftpserver/fake/command/PassCommandHandler.groovy
+++ b/MockFtpServer/src/main/groovy/org/mockftpserver/fake/command/PassCommandHandler.groovy
Binary files differ
diff --git a/MockFtpServer/src/main/groovy/org/mockftpserver/fake/command/UserCommandHandler.groovy b/MockFtpServer/src/main/groovy/org/mockftpserver/fake/command/UserCommandHandler.groovy
index c34fd7a..b91c7f6 100644
--- a/MockFtpServer/src/main/groovy/org/mockftpserver/fake/command/UserCommandHandler.groovy
+++ b/MockFtpServer/src/main/groovy/org/mockftpserver/fake/command/UserCommandHandler.groovy
Binary files differ
diff --git a/MockFtpServer/src/main/groovy/org/mockftpserver/fake/user/UserAccount.groovy b/MockFtpServer/src/main/groovy/org/mockftpserver/fake/user/UserAccount.groovy
index 09c71b9..625a9cd 100644
--- a/MockFtpServer/src/main/groovy/org/mockftpserver/fake/user/UserAccount.groovy
+++ b/MockFtpServer/src/main/groovy/org/mockftpserver/fake/user/UserAccount.groovy
@@ -30,22 +30,22 @@
  * value is ignored, and the <code>isValidPassword()</code> method just returns <code<true</code>. 
  */
 class UserAccount {
-    
+
     String username
     String password
     String homeDirectory
     boolean passwordRequiredForLogin = true
     boolean passwordCheckedDuringValidation = true
-    
+
     /**
      * Return true if the specified password is the correct, valid password for this user account.
      * This implementation uses standard (case-sensitive) String comparison. Subclasses can provide
      * custom comparison behavior, for instance using encrypted password values, by overriding this
      * method.
-     * 
+     *
      * @param password - the password to compare against the configured value
      * @return true if the password is correct and valid
-     * 
+     *
      * @throws AssertionError - if the username property is null
      */
     boolean isValidPassword(String password) {
@@ -54,19 +54,26 @@
     }
 
     /**
+     * @return true if this UserAccount object is valid; i.e. if the homeDirectory is non-null and non-empty.
+     */
+    boolean isValid() {
+        return homeDirectory
+    }
+
+    /**
      * @return the String representation of this object
      */
     String toString() {
         "UserAccount[username=$username; password=$password; homeDirectory=$homeDirectory; " +
-            "passwordRequiredForLogin=$passwordRequiredForLogin]"
+                "passwordRequiredForLogin=$passwordRequiredForLogin]"
     }
-    
+
     /**
      * Return true if the specified password matches the password configured for this user account.
      * This implementation uses standard (case-sensitive) String comparison. Subclasses can provide
      * custom comparison behavior, for instance using encrypted password values, by overriding this
      * method.
-     * 
+     *
      * @param password - the password to compare against the configured value
      * @return true if the passwords match
      */
diff --git a/MockFtpServer/src/main/java/org/mockftpserver/core/command/ReplyCodes.java b/MockFtpServer/src/main/java/org/mockftpserver/core/command/ReplyCodes.java
index c41f519..449181b 100644
--- a/MockFtpServer/src/main/java/org/mockftpserver/core/command/ReplyCodes.java
+++ b/MockFtpServer/src/main/java/org/mockftpserver/core/command/ReplyCodes.java
@@ -16,11 +16,10 @@
 package org.mockftpserver.core.command;

 

 /**

- * Reply Code constants. 

- * 

- * @version $Revision$ - $Date$

+ * Reply Code constants.

  *

  * @author Chris Mair

+ * @version $Revision$ - $Date$

  */

 public final class ReplyCodes {

 

@@ -55,7 +54,8 @@
     public static final int USER_LOGGED_IN_OK = 230;

     public static final int USER_NEED_PASSWORD_OK = 331;

     public static final int USER_NO_SUCH_USER = 530;

-    

+    public static final int USER_ACCOUNT_NOT_VALID = 530;

+

     public static final int SEND_DATA_INITIAL_OK = 150;

     public static final int SEND_DATA_FINAL_OK = 226;

 

@@ -69,8 +69,8 @@
     public static final int EXISTING_FILE_ERROR = 550;

     public static final int NEW_FILE_ERROR = 553;

     public static final int FILENAME_NOT_VALID = 553;

-    

-    

+

+

     /**

      * Private constructor. This class should not be instantiated.

      */

diff --git a/MockFtpServer/src/main/resources/ReplyText.properties b/MockFtpServer/src/main/resources/ReplyText.properties
index 37f6033..87e017a 100644
--- a/MockFtpServer/src/main/resources/ReplyText.properties
+++ b/MockFtpServer/src/main/resources/ReplyText.properties
@@ -60,3 +60,9 @@
 #    Exceeded storage allocation (for current directory or dataset).

 553=Requested action not taken for {0}

 #    File name not allowed.

+

+#-------------------------------------------------------------------------------

+# Other Messages

+#-------------------------------------------------------------------------------

+userAccountNotValid=UserAccount missing or invalid for user [{0}]

+homeDirectoryNotValid=The homeDirectory configured for user [{0}] is not a valid directory: [{1}]

diff --git a/MockFtpServer/src/test/groovy/org/mockftpserver/fake/StubServerConfiguration.groovy b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/StubServerConfiguration.groovy
index 99faa24..d858e44 100644
--- a/MockFtpServer/src/test/groovy/org/mockftpserver/fake/StubServerConfiguration.groovy
+++ b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/StubServerConfiguration.groovy
@@ -20,7 +20,7 @@
 import org.mockftpserver.fake.user.UserAccount

 

 /**

- * Stub implementation of the   {@link ServerConfiguration}   interface for testing

+ * Stub implementation of the    {@link ServerConfiguration}    interface for testing

  *

  * @version $Revision$ - $Date$

  *

@@ -46,12 +46,13 @@
     //-------------------------------------------------------------------------

 

     /**

-     * Set the text to be returned for the specified reply code by the

-     * {@link #getReplyTextBundle()}   method.

+     * Set the text to be returned for the specified key by the

+     * {@link #getReplyTextBundle()}  resource bundle.

      */

-    void setTextForReplyCode(int replyCode, String text) {

-        textForReplyCodeMap[replyCode as String] = text

+    void setTextForKey(key, String text) {

+        textForReplyCodeMap[key.toString()] = text

     }

+

 }

 

 /**

diff --git a/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/PassCommandHandlerTest.groovy b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/PassCommandHandlerTest.groovy
index f0a30a6..e02aff2 100644
--- a/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/PassCommandHandlerTest.groovy
+++ b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/PassCommandHandlerTest.groovy
Binary files differ
diff --git a/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/PwdCommandHandlerTest.groovy b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/PwdCommandHandlerTest.groovy
index 296828b..cf6c1a4 100644
--- a/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/PwdCommandHandlerTest.groovy
+++ b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/PwdCommandHandlerTest.groovy
Binary files differ
diff --git a/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/UserCommandHandlerTest.groovy b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/UserCommandHandlerTest.groovy
index 3a19bc1..86f3efb 100644
--- a/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/UserCommandHandlerTest.groovy
+++ b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/UserCommandHandlerTest.groovy
Binary files differ
diff --git a/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/_AbstractFakeCommandHandlerTest.groovy b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/_AbstractFakeCommandHandlerTest.groovy
index 8a1cfad..1b7a49d 100644
--- a/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/_AbstractFakeCommandHandlerTest.groovy
+++ b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/command/_AbstractFakeCommandHandlerTest.groovy
@@ -42,9 +42,11 @@
 
     static PATH = "some/path"
     static REPLY_CODE = 99
+    static MESSAGE_KEY = "99.WithFilename"
     static ARG = "ABC"
     static MSG = "text {0}"
     static MSG_WITH_ARG = "text ABC"
+    static MSG_FOR_KEY = "some other message"
     private AbstractFakeCommandHandler commandHandler
     private session
     private serverConfiguration
@@ -87,6 +89,14 @@
         shouldFail { commandHandler.sendReply(session, 0) }
     }
 
+    void testSendReply_MessageKey() {
+        commandHandler.sendReply(session, REPLY_CODE, MESSAGE_KEY)
+        assert session.sentReplies[0] == [REPLY_CODE, MSG_FOR_KEY], session.sentReplies[0]
+
+        shouldFail { commandHandler.sendReply(null, REPLY_CODE, MESSAGE_KEY) }
+        shouldFail { commandHandler.sendReply(session, 0, MESSAGE_KEY) }
+    }
+
     void testAssertValidReplyCode() {
         commandHandler.assertValidReplyCode(1)        // no exception expected
         shouldFail { commandHandler.assertValidReplyCode(0) }
@@ -145,7 +155,8 @@
         fileSystem = new FakeUnixFileSystem()
         serverConfiguration.setFileSystem(fileSystem)
 
-        serverConfiguration.setTextForReplyCode(REPLY_CODE, MSG)
+        serverConfiguration.setTextForKey(REPLY_CODE, MSG)
+        serverConfiguration.setTextForKey(MESSAGE_KEY, MSG_FOR_KEY)
 
         commandHandler.serverConfiguration = serverConfiguration
     }
diff --git a/MockFtpServer/src/test/groovy/org/mockftpserver/fake/server/FakeFtpServerIntegrationTest.groovy b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/server/FakeFtpServerIntegrationTest.groovy
new file mode 100644
index 0000000..7ae2b13
--- /dev/null
+++ b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/server/FakeFtpServerIntegrationTest.groovy
@@ -0,0 +1,138 @@
+/*

+ * Copyright 2008 the original author or authors.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ * 

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ * 

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+package org.mockftpserver.fake.server

+

+import org.apache.commons.net.ftp.FTPClient

+import org.mockftpserver.fake.filesystem.FakeWindowsFileSystem

+import org.mockftpserver.fake.filesystem.FileSystem

+import org.mockftpserver.fake.user.UserAccount

+import org.mockftpserver.test.AbstractGroovyTest

+import org.mockftpserver.test.PortTestUtil

+

+/**

+ * Integration tests for FakeFtpServer.

+ *

+ * @version $Revision: 54 $ - $Date: 2008-05-13 21:54:53 -0400 (Tue, 13 May 2008) $

+ *

+ * @author Chris Mair

+ */

+class FakeFtpServerIntegrationTest extends AbstractGroovyTest {

+

+    static final SERVER = "localhost";

+    static final USERNAME = "user123";

+    static final PASSWORD = "password";

+    static final FILENAME = "abc.txt";

+    static final ASCII_CONTENTS = "abcdef\tghijklmnopqr";

+    static final BINARY_CONTENTS = new byte[256];

+    static final HOME_DIRECTORY = "/"

+

+    private FakeFtpServer ftpServer;

+    private FTPClient ftpClient;

+    private FileSystem fileSystem

+

+    //-------------------------------------------------------------------------

+    // Tests

+    //-------------------------------------------------------------------------

+

+    void testLogin() {

+        // Connect

+        LOG.info("Conecting to " + SERVER)

+        ftpClientConnect()

+        verifyReplyCode("connect", 220)

+

+        // Login

+        String userAndPassword = USERNAME + "/" + PASSWORD

+        LOG.info("Logging in as " + userAndPassword)

+        boolean success = ftpClient.login(USERNAME, PASSWORD)

+        assertTrue("Unable to login with " + userAndPassword, success)

+        verifyReplyCode("login with " + userAndPassword, 230)

+

+        // Quit

+        ftpClient.quit()

+        verifyReplyCode("quit", 221)

+    }

+

+//    void testPwd() {

+//        ftpClientConnect()

+//

+//        String dir = ftpClient.printWorkingDirectory()

+//        assert dir == DIR

+//        verifyReplyCode("printWorkingDirectory", 257)

+//    }

+

+    // -------------------------------------------------------------------------

+    // Test setup and tear-down

+    // -------------------------------------------------------------------------

+

+    /**

+     * Perform initialization before each test

+     * @see org.mockftpserver.test.AbstractTest#setUp()

+     */

+    void setUp() {

+        super.setUp();

+

+        for (int i = 0; i < BINARY_CONTENTS.length; i++) {

+            BINARY_CONTENTS[i] = (byte) i;

+        }

+

+        ftpServer = new FakeFtpServer();

+        ftpServer.setServerControlPort(PortTestUtil.getFtpServerControlPort());

+

+        fileSystem = new FakeWindowsFileSystem()

+        ftpServer.fileSystem = fileSystem

+        fileSystem.createDirectory(HOME_DIRECTORY)

+

+        def userAccount = new UserAccount(username: USERNAME, password: PASSWORD,

+                passwordRequiredForLogin: true, homeDirectory: HOME_DIRECTORY)

+        ftpServer.userAccounts[USERNAME] = userAccount

+

+        ftpServer.start();

+        ftpClient = new FTPClient();

+    }

+

+    /**

+     * Perform cleanup after each test

+     * @see org.mockftpserver.test.AbstractTest#tearDown()

+     */

+    void tearDown() {

+        super.tearDown();

+        ftpServer.stop();

+    }

+

+    // -------------------------------------------------------------------------

+    // Internal Helper Methods

+    // -------------------------------------------------------------------------

+

+    /**

+     * Connect to the server from the FTPClient

+     */

+    private void ftpClientConnect() throws IOException {

+        ftpClient.connect(SERVER, PortTestUtil.getFtpServerControlPort());

+    }

+

+    /**

+     * Assert that the FtpClient reply code is equal to the expected value

+     *

+     * @param operation - the description of the operation performed; used in the error message

+     * @param expectedReplyCode - the expected FtpClient reply code

+     */

+    private void verifyReplyCode(String operation, int expectedReplyCode) {

+        int replyCode = ftpClient.getReplyCode();

+        LOG.info("Reply: operation=\"" + operation + "\" replyCode=" + replyCode);

+        assertEquals("Unexpected replyCode for " + operation, expectedReplyCode, replyCode);

+    }

+

+}
\ No newline at end of file
diff --git a/MockFtpServer/src/test/groovy/org/mockftpserver/fake/user/UserAccountTest.groovy b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/user/UserAccountTest.groovy
index 19ca6bb..5e69008 100644
--- a/MockFtpServer/src/test/groovy/org/mockftpserver/fake/user/UserAccountTest.groovy
+++ b/MockFtpServer/src/test/groovy/org/mockftpserver/fake/user/UserAccountTest.groovy
@@ -15,11 +15,11 @@
  */
 package org.mockftpserver.fake.user
 
-import org.mockftpserver.test.*
+import org.mockftpserver.test.AbstractGroovyTest
 
 /**
  * Tests for UserAccount
- * 
+ *
  * @version $Revision$ - $Date$
  *
  * @author Chris Mair
@@ -31,25 +31,25 @@
     private static final HOME_DIR = "/usr/user123"
 
     private UserAccount userAccount
-    
+
     void testIsValidPassword() {
         userAccount.username = USERNAME
         userAccount.password = PASSWORD
         assert userAccount.isValidPassword(PASSWORD)
-        
+
         assert userAccount.isValidPassword("") == false
         assert userAccount.isValidPassword("wrong") == false
         assert userAccount.isValidPassword(null) == false
     }
-    
+
     void testIsValidPassword_UsernameNullOrEmpty() {
         userAccount.password = PASSWORD
         shouldFail(AssertionError) { userAccount.isValidPassword(PASSWORD) }
 
         userAccount.username = ''
         shouldFail(AssertionError) { userAccount.isValidPassword(PASSWORD) }
-	}
-    
+    }
+
     void testIsValidPassword_OverrideComparePassword() {
         def customUserAccount = new CustomUserAccount()
         customUserAccount.username = USERNAME
@@ -58,14 +58,22 @@
         assert customUserAccount.isValidPassword(PASSWORD) == false
         assert customUserAccount.isValidPassword(PASSWORD + "123")
     }
-    
+
     void testIsValidPassword_PasswordNotCheckedDuringValidation() {
         userAccount.username = USERNAME
         userAccount.password = PASSWORD
         userAccount.passwordCheckedDuringValidation = false
         assert userAccount.isValidPassword("wrong")
     }
-    
+
+    void testIsValid() {
+        assert !userAccount.valid
+        userAccount.homeDirectory = ""
+        assert !userAccount.valid
+        userAccount.homeDirectory = "/abc"
+        assert userAccount.valid
+    }
+
     void setUp() {
         userAccount = new UserAccount()
     }