RavenDB - Image Gallery Project (VIII) - Signing In + RavenDB Testing

Published on 2010-10-7

The code for this and all other entries can be found here: http://github.com/robashton/RavenGallery/ 

With registration conquered, the next challenge is to allow users to sign in and out, authenticating against RavenDB’s document store and telling ASP.NET who the current user is. This should be a breeze now everything else is in place.

Post-registration log-on

This is a simplistic system, and we just want the ability to log in for now, so we’re not going to bother with user e-mail address validation or OpenId, although that might be something we could visit later, the ability to log in and out is a fairly fundamental blocker when it comes to adding the rest of the functionality.

First thing we need to do disassociate ourselves from the FormsAuthentication static class or anything like it, out of principle I don’t use any of the built in provider garbage in MS-MVC, implementing enormous interfaces isn’t my idea of fun and I’ve never managed to work out what else it gives me. I do however like to use FormsAuthentication to manage cookies for me.

I just want to be able to test if I want to, and I do want to test – even if it’s just for simple interaction.

   1:      public interface IAuthenticationService
   2:      {
   3:          void SignIn(string username, bool persistent);
   4:          void SignOut();
   5:      }
 
   1:      public class AuthenticationService : IAuthenticationService
   2:      {
   3:          public void SignIn(string username, bool persistent)
   4:          {
   5:              FormsAuthentication.SetAuthCookie(username, persistent);
   6:          }
   7:   
   8:          public void SignOut()
   9:          {
  10:              FormsAuthentication.SignOut();
  11:          }
  12:      }

This is just an implementation detail and not worth going into further, the thrust of this now is that straight after registration, we’re going to log in. We’re not going to wait for the user document to get indexed into any views and we’re not going to wait for confirmation that all went well – the command didn’t throw an exception and that’s good enough for us.

   1:         [AcceptVerbs(HttpVerbs.Post)]
   2:          public ActionResult Register(UserRegisterViewModel model)
   3:          {
   4:              if (ModelState.IsValid)
   5:              {
   6:                  commandInvoker.Execute(new RegisterNewUserCommand(model.Username, model.Password));
   7:                  authenticationService.SignIn(model.Username, model.StayLoggedIn);
   8:                  return RedirectToAction("Index", "Home");
   9:              }
  10:              else
  11:              {
  12:                  return View(model);
  13:              }            
  14:          }

For those who have asked, while I am employing TDD whilst writing the entire application (I have tests for the above action that I wrote before I wrote the action), I’m not talking about them as they aren’t directly or indirectly related with RavenDB.

Returning visitor sign-in

As before I have created a view model (with Username, Password and StayLoggedIn on it) and a form that uses this on the SignIn page. I am going to use the validator to check the credentials along with other logic.

   1:     public class UserSignInViewModelValidator : AbstractValidator<UserSignInViewModel>
   2:      {
   3:          public UserSignInViewModelValidator(IUserService userService)
   4:          {
   5:              this.RuleFor(x => x.Username)
   6:                  .NotEmpty()
   7:                  .Must((model, property) => userService.DoesUserExistWithUsernameAndPassword(model.Username, model.Password))
   8:                      .WithMessage("User/password combination does not exist in our system");
   9:   
  10:              this.RuleFor(x => x.Password)
  11:                  .NotEmpty();
  12:          }
  13:      }

As you can see, I’ve added another method to our service IUserService, this still doesn’t feel right but I’m confident I’ll come up with a better solution so I keep on going and write a test for that as I did before.

   1:          [Test]
   2:          public void WhenUserExists_DoesUserExistWithUsernameAndPassword_ReturnsTrue()
   3:          {
   4:              using (var session = Store.OpenSession())
   5:              {
   6:                  session.Store(new UserDocument()
   7:                  {
   8:                      PasswordHash = HashUtil.HashPassword("password"),
   9:                      Username = "testUser"
  10:                  });
  11:                  session.SaveChanges();
  12:   
  13:                  UserService service = new UserService(session);
  14:                  bool result = service.DoesUserExistWithUsernameAndPassword("testUser", "password");
  15:                  Assert.True(result);
  16:              }
  17:          }
  18:   
  19:          [Test]
  20:          public void WhenUserDoesNotExist_DoesUserExistWithUsernameAndPassword_ReturnsFalse()
  21:          {
  22:              using (var session = Store.OpenSession())
  23:              {
  24:                  session.Store(new UserDocument()
  25:                  {
  26:                      PasswordHash = HashUtil.HashPassword("password"),
  27:                      Username = "testUser"
  28:                  });
  29:                  session.SaveChanges();
  30:   
  31:                  UserService service = new UserService(session);
  32:                  bool result = service.DoesUserExistWithUsernameAndPassword("testUser", "password2");
  33:                  Assert.False(result);
  34:              }
  35:          }

Again this means that all I have to do in my controller is check for a valid model state before authenticating against the service we created earlier

   1:         [AcceptVerbs(HttpVerbs.Post)]
   2:          public ActionResult SignIn(UserSignInViewModel model)
   3:          {
   4:              if (ModelState.IsValid)
   5:              {
   6:                  authenticationService.SignIn(model.Username, model.StayLoggedIn);
   7:                  return RedirectToAction("Index", "Home");
   8:              }
   9:              else
  10:              {
  11:                  return View(model);
  12:              }          
  13:          }

Hang on a second, you keep writing your tests against an actual RavenDB instance

I’ve only actually shown a couple of tests so far, because we’ve only had a small amount of direct interaction with RavenDB, the problem is that when testing queries the only reliable way to see if they work is to execute them in the system under test (an integration test effectively). I could be really clever here and write a Linq provider that substitutes RavenDB for an in memory lucene index or something radical like that, but I’m not that bright and wouldn’t know where to begin, so I keep on starting up RavenDB in the tests that need it by inheriting from the following test class:

    public class LocalRavenTest
    {
        private EmbeddableDocumentStore store;
        public EmbeddableDocumentStore Store { get { return store; } }
 
        [SetUp]
        public void CreateStore()
        {
            store = new EmbeddableDocumentStore
            {
                Configuration = new RavenConfiguration
                {
                    RunInMemory = true
                }
            };
            store.Initialize();
            IndexCreation.CreateIndexes(typeof(ImageTags_GroupByTagName).Assembly, store);
        }
 
        [TearDown]
        public void DestroyStore()
        {
            store.Dispose();
        }
 
        public void WaitForIndexing()
        {
            while (store.DocumentDatabase.Statistics.StaleIndexes.Length > 0)
            {
                Thread.Sleep(100);
            }
        }
    }

This is pretty cool, RavenDB can run in memory which means not having to perform masses of IO during testing – this keeps our tests nice and fast and allows us to write tests against all of our  RavenDB integration. (A preference when developing against the unstable branch)

I take some time out of this busy development schedule to create a new Assembly, RavenGallery.Core.Tests.Integration.dll and move those tests to it. They might be fast, but fast is relative and I still don’t want to be waiting for these every time I want to test my other code.

In the next instalment, we’ll look at our document model for storing images with some rudimentary support for tagging and labelling, it’s about to get interesting.

2020 © Rob Ashton. ALL Rights Reserved.