Logon Workshop

Setup

Run our legacy project

  • Download git repository
    git clone 
    https://[email protected]/ptrstpp950/logonworkshop.git
  • Run Visual Studio
  • Compile&run with CTRL+F5 and listen to the story
  • Example login is test and password is 123
  • Who am I?

  • User of every useful technology
  • DevOps and automation adept
  • Working in mBank as IT Expert
  • The story

    Long long time ago, far far away in our company somebody created a login page :)

    It has small feature: when user 3 times in a row enters bad credentials, the login is blocked for 1 minute

    The users frequently create service calls about blocked account

    The boss said: Do something with it!!

    And now your are here looking into the code

    Let's create a Unit test project

    References

    Our first test method

    [TestMethod]
    public void TestMethodSuccessLogin()
    {
        var password = "123";
        var user = "test";
        var controller = new HomeController();
        controller.Index(new LoginModel() {User = user, Password = password});
    }
    

    Connection string problem

    We need to add it in the constructor of HomeController

    private string connectionString;
    public HomeController() : this
        (ConfigurationManager.ConnectionStrings["ProjectDB"].ConnectionString)
    {
    
    }
    public HomeController(string connectionString)
    {
        this.connectionString = connectionString;
    }
    

    Fix unit test

    Options

    Extract SQL methods

    Eliminate SQLConnection from HomeController

    This is dangerous part

    SqlRepository code

    public SqlConnection conn;
    public SqlRepository(string connectionString)
    {
        conn = new SqlConnection(connectionString);
    }
    public void Dispose()
    {
        if (conn != null)
            conn.Dispose();
    }
    public void Open()
    {
        conn.Open();
    }
    

    Interface is easier to stub/mock

    To convert SqlRepository just use Visual Studio right-click menu
    Refactor->Extract interface

    Remember to make this interface inherit from IDisposable

    If you are lost

    If you are lost just checkout step2 branch from git with:
    git checkout step2
    

    Use Func Luke!

    The last step is to eliminate

    using (var sqlRepository = new SqlRepository(connectionString))

    Easy conversion is to use Func like:

    public Func CreateSqlRepository;
    public HomeController(string connectionString)
    {
        this.connectionString = connectionString;
        CreateSqlRepository = () => new SqlRepository(connectionString);
    }
    

    Let's write stub

    public class SqlRepositoryStub : ISqlRepository
    {
        public class UserInDbStub
        {
            public int UserId;
            public string Username;
            public string Password;
            public DateTime LastBadLogin = new DateTime();
            public int BadAttempt = 0;
        }
        public List Users = new List();
        
        public int GetUserId(LoginModel model)
        {
            var user = 
              Users.FirstOrDefault(x => 
                x.Username == model.User);
            if (user == null)
            {
                return -1;
            }
            return user.UserId;
        }
        
        public void TryLogin(LoginModel model, int userId,
            out int badAttempt, out DateTime lastBadLogin, 
            out bool successLogin)
        {
            var user = 
              Users.FirstOrDefault(x => 
                x.UserId == userId 
                && x.Password == model.Password);
            if (user == null)
            {
                lastBadLogin = DateTime.MinValue;
                badAttempt = 0;
                successLogin = false;
            }
            lastBadLogin = user.LastBadLogin;
            badAttempt = user.BadAttempt;
            successLogin = true;
        }
        
        public void UpdateBadAttemps(int userId)
        {
            var user = 
            Users.First(x => x.UserId == userId);
            user.BadAttempt += 1;
            user.LastBadLogin = DateTime.Now;
        }
        
        public void Open()
        {
        }
        public void Dispose()
        {
        }
    }
    
    
    

    Our first test method

    var password = "123";
    var user = "test";
    var controller = new HomeController("");
    controller.CreateSqlRepository = () =>
    {
        var sqlRepositoryStub = new SqlRepositoryStub();
        
        sqlRepositoryStub.Users.Add(
        new SqlRepositoryStub.UserInDbStub()
        {
            BadAttempt = 0,
            UserId = 1,
            Username = user,
            Password = password
        });
        return sqlRepositoryStub;
    };
    var result = controller
        .Index(
          new LoginModel() {User = user, Password = password}) 
        as RedirectToRouteResult;
    Assert.IsNotNull(result);
    Assert.AreEqual("LoginSuccess", result.RouteValues["action"]);
    

    Now is Your turn

    The code has two errors - find them wit Unit Test