package gov.va.med.edp.springframework.security.providers.vistalink;

import gov.va.med.edp.springframework.security.userdetails.VistaUserDetails;
import gov.va.med.edp.springframework.security.userdetails.VistaUserDetailsService;
import junit.framework.TestCase;
import org.springframework.security.*;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.providers.dao.UserCache;
import org.springframework.security.providers.dao.cache.NullUserCache;
import org.springframework.security.providers.x509.X509AuthenticationToken;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.dao.DataRetrievalFailureException;
import org.easymock.MockControl;

import java.util.HashMap;
import java.util.Map;

public class VistaAuthenticationProviderTest extends TestCase {

    private static final String TEST_REMOTE_ADDRESS = "192.168.0.1";
    private static final String TEST_STATION_NUMBER = "663";
    private static final String TEST_ACCESS = "10VEHU";
    private static final String TEST_VERIFY = "VEHU10";
    private static final String TEST_DUZ = "12345";

    private VistaUserDetails user;
    private VistaAuthenticationProviderTest.MockUserCache mockCache;

    private MockControl mockUserDetailServiceControl;
    private VistaUserDetailsService mockUserDetailService;
    private VistaAuthenticationProvider provider;


    protected void setUp() throws Exception {
        user = createUser("54321.123456789", TEST_STATION_NUMBER, TEST_DUZ, TEST_ACCESS + ";" + TEST_VERIFY + ";" + TEST_REMOTE_ADDRESS, true, true, true, true, new GrantedAuthority[]{new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO")});

        mockUserDetailServiceControl = MockControl.createControl(VistaUserDetailsService.class);
        mockUserDetailService = (VistaUserDetailsService) mockUserDetailServiceControl.getMock();

        provider = new VistaAuthenticationProvider();
        provider.setUserDetailsService(mockUserDetailService);
        mockCache = new MockUserCache();
        provider.setUserCache(mockCache);
        provider.afterPropertiesSet();
    }

    protected VistaUserDetails createUser(String logIEN, String stationNumber, String duz, String password, boolean nonExpired, boolean nonLocked, boolean credentialsNonExpired, boolean enabled, GrantedAuthority[] authorities) {
        MockControl mockUserControl = MockControl.createControl(VistaUserDetails.class);
        VistaUserDetails user = (VistaUserDetails) mockUserControl.getMock();
        mockUserControl.expectAndDefaultReturn(user.getSignonLogInternalEntryNumber(), logIEN);
        mockUserControl.expectAndDefaultReturn(user.getLoginStationNumber(), stationNumber);
        mockUserControl.expectAndDefaultReturn(user.getDuz(), duz);
        mockUserControl.expectAndDefaultReturn(user.isAccountNonExpired(), nonExpired);
        mockUserControl.expectAndDefaultReturn(user.isAccountNonLocked(), nonLocked);
        mockUserControl.expectAndDefaultReturn(user.isCredentialsNonExpired(), credentialsNonExpired);
        mockUserControl.expectAndDefaultReturn(user.isEnabled(), enabled);
        mockUserControl.expectAndDefaultReturn(user.getUsername(), duz + "@" + stationNumber);
        mockUserControl.expectAndDefaultReturn(user.getPassword(), password);
        mockUserControl.expectAndDefaultReturn(user.getAuthorities(), authorities);
        mockUserControl.replay();
        return user;
    }

    public void testSupports() {
        VistaAuthenticationProvider provider = new VistaAuthenticationProvider();

        assertTrue(provider.supports(VistaAuthenticationToken.class));
        assertFalse(provider.supports(UsernamePasswordAuthenticationToken.class));
        assertFalse(provider.supports(X509AuthenticationToken.class));
    }

    public void testReceivedBadCredentialsWhenCredentialsNotProvided() {
        mockUserDetailServiceControl.expectAndThrow(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, null, null), new BadCredentialsException("missing credentials"));
        mockUserDetailServiceControl.replay();

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, null, null);
        try {
            provider.authenticate(token);
            fail("Expected BadCredenialsException");
        } catch (BadCredentialsException expected) {
            // NOOP
        }

        mockUserDetailServiceControl.verify();
    }

    public void testAuthenticateFailsIfAccountExpired() {
        user = createUser("54321.123456789", TEST_STATION_NUMBER, TEST_DUZ, null, false, true, true, true, new GrantedAuthority[]{});
        mockUserDetailServiceControl.expectAndReturn(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS), user);
        mockUserDetailServiceControl.replay();

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);

        try {
            provider.authenticate(token);
            fail("Should have thrown AccountExpiredException");
        } catch (AccountExpiredException expected) {
            assertTrue(true);
        }
    }

    public void testAuthenticateFailsIfAccountLocked() {
        user = createUser("54321.123456789", TEST_STATION_NUMBER, TEST_DUZ, null, true, false, true, true, new GrantedAuthority[]{});
        mockUserDetailServiceControl.expectAndReturn(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS), user);
        mockUserDetailServiceControl.replay();

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);

        try {
            provider.authenticate(token);
            fail("Should have thrown LockedException");
        } catch (LockedException expected) {
            assertTrue(true);
        }
    }

    public void testAuthenticateFailsIfCredentialsExpired() {
        user = createUser("54321.123456789", TEST_STATION_NUMBER, TEST_DUZ, null, true, true, false, true, new GrantedAuthority[]{});
        mockUserDetailServiceControl.expectAndReturn(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS), user);
        mockUserDetailServiceControl.replay();

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);
        try {
            provider.authenticate(token);
            fail("Expected CredentialsExpiredException");
        } catch (CredentialsExpiredException expected) {
            // NOOP
        }

        mockUserDetailServiceControl.verify();
    }

    public void testAuthenticateFailsIfUserDisabled() {
        user = createUser("54321.123456789", TEST_STATION_NUMBER, TEST_DUZ, null, true, true, true, false, new GrantedAuthority[]{});
        mockUserDetailServiceControl.expectAndReturn(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS), user);
        mockUserDetailServiceControl.replay();

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);

        try {
            provider.authenticate(token);
            fail("Should have thrown DisabledException");
        } catch (DisabledException expected) {
            assertTrue(true);
        }
    }


    public void testAuthenticateFailsWhenAuthenticationDaoHasBackendFailure() {
        mockUserDetailServiceControl.expectAndThrow(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS), new DataRetrievalFailureException("This mock simulator is designed to fail"));
        mockUserDetailServiceControl.replay();

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);
        try {
            provider.authenticate(token);
            fail("Should have thrown AuthenticationServiceException");
        } catch (AuthenticationServiceException expected) {
            assertTrue(true);
        }

        mockUserDetailServiceControl.verify();
    }

    public void testAuthenticates() {
        mockUserDetailServiceControl.expectAndReturn(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS), user);
        mockUserDetailServiceControl.replay();

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);
        Authentication result = provider.authenticate(token);

        if (!(result instanceof VistaAuthenticationToken)) {
            fail("Should have returned instance of VistaAuthenticationToken");
        }
        assertNotSame(token, result);

        VistaAuthenticationToken castResult = (VistaAuthenticationToken) result;
        assertTrue(VistaUserDetails.class.isAssignableFrom(castResult.getPrincipal().getClass()));
        assertEquals(TEST_ACCESS, castResult.getAccessCode());
        assertEquals(TEST_VERIFY, castResult.getVerifyCode());
        assertEquals("ROLE_ONE", castResult.getAuthorities()[0].getAuthority());
        assertEquals("ROLE_TWO", castResult.getAuthorities()[1].getAuthority());
        assertEquals(TEST_REMOTE_ADDRESS, castResult.getDetails());

        mockUserDetailServiceControl.verify();
    }

    public void testAuthenticatesASecondTime() {
        mockUserDetailServiceControl.expectAndReturn(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS),user);
        mockUserDetailServiceControl.replay();

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);

        Authentication result = provider.authenticate(token);

        if (!(result instanceof VistaAuthenticationToken)) {
            fail("Should have returned instance of VistaAuthenticationToken");
        }

        // Now try to authenticate with the previous result (with its UserDetails)
        Authentication result2 = provider.authenticate(result);

        if (!(result2 instanceof VistaAuthenticationToken)) {
            fail("Should have returned instance of VistaAuthenticationToken");
        }

        assertNotSame(result, result2);
        assertEquals(result.getCredentials(), result2.getCredentials());
    }

    public void testDetectsNullBeingReturnedFromAuthenticationDao() {
        mockUserDetailServiceControl.expectAndReturn(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS), null);
        mockUserDetailServiceControl.replay();

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);

        try {
            provider.authenticate(token);
            fail("Should have thrown AuthenticationServiceException");
        } catch (AuthenticationServiceException expected) {
            assertEquals("VistaUserDetailsService returned null, which is an interface contract violation",
                    expected.getMessage());
        }
    }

    public void testGoesBackToAuthenticationDaoToObtainLatestVerifyCodeIfCachedVerifyCodeSeemsIncorrect() {
        mockUserDetailServiceControl.expectAndReturn(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS), user);
        mockUserDetailServiceControl.replay();

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);

        // This will work, as password still "koala"
        provider.authenticate(token);

        // Check "12345@663 = 10VEHU;VEHU10;192.168.0.1" ended up in the cache
        assertEquals(TEST_ACCESS + ";" + TEST_VERIFY + ";" + TEST_REMOTE_ADDRESS, mockCache.getUserFromCache(TEST_DUZ + "@" + TEST_STATION_NUMBER).getPassword());
        mockUserDetailServiceControl.verify();

        // Now change the password the AuthenticationDao will return
        mockUserDetailServiceControl.reset();

        user = createUser("54321.123456789", TEST_STATION_NUMBER, TEST_DUZ, TEST_ACCESS + ";easternLongNeckTurtle;" + TEST_REMOTE_ADDRESS, true, true, true, true, new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO")});
        mockUserDetailServiceControl.expectAndReturn(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, "easternLongNeckTurtle", TEST_REMOTE_ADDRESS), user);
        mockUserDetailServiceControl.replay();

        // Now try authentication again, with the new password
        token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, "easternLongNeckTurtle", TEST_REMOTE_ADDRESS);
        provider.authenticate(token);

        // To get this far, the new password was accepted
        // Check the cache was updated
        assertEquals("10VEHU;easternLongNeckTurtle;192.168.0.1", mockCache.getUserFromCache(TEST_DUZ + "@" + TEST_STATION_NUMBER).getPassword());
    }

    public void testStartupFailsIfNoVistaUserDetailsService()
            throws Exception {
        VistaAuthenticationProvider provider = new VistaAuthenticationProvider();

        try {
            provider.afterPropertiesSet();
            fail("Should have thrown IllegalArgumentException");
        } catch (IllegalArgumentException expected) {
            assertTrue(true);
        }
    }

    public void testStartupFailsIfNoUserCacheSet() throws Exception {
        VistaAuthenticationProvider provider = new VistaAuthenticationProvider();
        provider.setUserDetailsService(mockUserDetailService);
        assertEquals(NullUserCache.class, provider.getUserCache().getClass());
        provider.setUserCache(null);

        try {
            provider.afterPropertiesSet();
            fail("Should have thrown IllegalArgumentException");
        } catch (IllegalArgumentException expected) {
            assertTrue(true);
        }
    }

    public void testStartupSuccess() throws Exception {
        VistaAuthenticationProvider provider = new VistaAuthenticationProvider();
        provider.setUserDetailsService(mockUserDetailService);
        provider.setUserCache(new MockUserCache());
        assertSame(mockUserDetailService, provider.getUserDetailsService());
        provider.afterPropertiesSet();
    }

    private class MockUserCache implements UserCache {
        private Map cache = new HashMap();

        public UserDetails getUserFromCache(String username) {
            return (UserDetails) cache.get(username);
        }

        public void putUserInCache(UserDetails user) {
            cache.put(user.getUsername(), user);
        }

        public void removeUserFromCache(String username) {
        }
    }
}

/* this is the source for when we move to java 5 an later easymock version
import gov.va.med.edp.springframework.security.userdetails.VistaUserDetails;
import gov.va.med.edp.springframework.security.userdetails.VistaUserDetailsService;
import junit.framework.TestCase;
import org.springframework.security.*;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.providers.dao.UserCache;
import org.springframework.security.providers.dao.cache.NullUserCache;
import org.springframework.security.providers.x509.X509AuthenticationToken;
import org.springframework.security.userdetails.UserDetails;
import org.easymock.EasyMock;
import static org.easymock.EasyMock.*;
import org.springframework.dao.DataRetrievalFailureException;

import java.util.HashMap;
import java.util.Map;

public class VistaAuthenticationProviderTest extends TestCase {

    private static final String TEST_REMOTE_ADDRESS = "192.168.0.1";
    private static final String TEST_STATION_NUMBER = "663";
    private static final String TEST_ACCESS = "10VEHU";
    private static final String TEST_VERIFY = "VEHU10";
    private static final String TEST_DUZ = "12345";

    private VistaUserDetails user;
    private VistaAuthenticationProviderTest.MockUserCache mockCache;

    private VistaUserDetailsService mockUserDetailService;
    private VistaAuthenticationProvider provider;


    protected void setUp() throws Exception {
        user = createUser("54321.123456789", TEST_STATION_NUMBER, TEST_DUZ, TEST_ACCESS + ";" + TEST_VERIFY + ";" + TEST_REMOTE_ADDRESS, true, true, true, true, new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO"));

        mockUserDetailService = EasyMock.createMock(VistaUserDetailsService.class);

        provider = new VistaAuthenticationProvider();
        provider.setUserDetailsService(mockUserDetailService);
        mockCache = new MockUserCache();
        provider.setUserCache(mockCache);
        provider.afterPropertiesSet();
    }

    protected VistaUserDetails createUser(String logIEN, String stationNumber, String duz, String password, boolean nonExpired, boolean nonLocked, boolean credentialsNonExpired, boolean enabled, GrantedAuthority... authorities) {
        VistaUserDetails user = EasyMock.createMock(VistaUserDetails.class);
        EasyMock.expect(user.getSignonLogInternalEntryNumber()).andReturn(logIEN).anyTimes();
        EasyMock.expect(user.getLoginStationNumber()).andReturn(stationNumber).anyTimes();
        EasyMock.expect(user.getDuz()).andReturn(duz).anyTimes();
        EasyMock.expect(user.isAccountNonExpired()).andReturn(nonExpired).anyTimes();
        EasyMock.expect(user.isAccountNonLocked()).andReturn(nonLocked).anyTimes();
        EasyMock.expect(user.isCredentialsNonExpired()).andReturn(credentialsNonExpired).anyTimes();
        EasyMock.expect(user.isEnabled()).andReturn(enabled).anyTimes();
        EasyMock.expect(user.getUsername()).andReturn(duz + "@" + stationNumber).anyTimes();
        EasyMock.expect(user.getPassword()).andReturn(password).anyTimes();
        EasyMock.expect(user.getAuthorities()).andReturn(authorities).anyTimes();
        EasyMock.replay(user);
        return user;
    }

    public void testSupports() {
        VistaAuthenticationProvider provider = new VistaAuthenticationProvider();

        assertTrue(provider.supports(VistaAuthenticationToken.class));
        assertFalse(provider.supports(UsernamePasswordAuthenticationToken.class));
        assertFalse(provider.supports(X509AuthenticationToken.class));
    }

    public void testReceivedBadCredentialsWhenCredentialsNotProvided() {
        expect(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, null, null)).andThrow(new BadCredentialsException("missing credentials"));
        replay(mockUserDetailService);

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, null, null);
        try {
            provider.authenticate(token);
            fail("Expected BadCredenialsException");
        } catch (BadCredentialsException expected) {
            // NOOP
        }

        verify(mockUserDetailService);
    }

    public void testAuthenticateFailsIfAccountExpired() {
        user = createUser("54321.123456789", TEST_STATION_NUMBER, TEST_DUZ, null, false, true, true, true);
        expect(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS)).andReturn(user);
        replay(mockUserDetailService);

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);

        try {
            provider.authenticate(token);
            fail("Should have thrown AccountExpiredException");
        } catch (AccountExpiredException expected) {
            assertTrue(true);
        }
    }

    public void testAuthenticateFailsIfAccountLocked() {
        user = createUser("54321.123456789", TEST_STATION_NUMBER, TEST_DUZ, null, true, false, true, true);
        expect(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS)).andReturn(user);
        replay(mockUserDetailService);

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);

        try {
            provider.authenticate(token);
            fail("Should have thrown LockedException");
        } catch (LockedException expected) {
            assertTrue(true);
        }
    }

    public void testAuthenticateFailsIfCredentialsExpired() {
        user = createUser("54321.123456789", TEST_STATION_NUMBER, TEST_DUZ, null, true, true, false, true);
        expect(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS)).andReturn(user);
        replay(mockUserDetailService);

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);
        try {
            provider.authenticate(token);
            fail("Expected CredentialsExpiredException");
        } catch (CredentialsExpiredException expected) {
            // NOOP
        }

        verify(mockUserDetailService);
    }

    public void testAuthenticateFailsIfUserDisabled() {
        user = createUser("54321.123456789", TEST_STATION_NUMBER, TEST_DUZ, null, true, true, true, false);
        expect(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS)).andReturn(user);
        replay(mockUserDetailService);

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);

        try {
            provider.authenticate(token);
            fail("Should have thrown DisabledException");
        } catch (DisabledException expected) {
            assertTrue(true);
        }
    }


    public void testAuthenticateFailsWhenAuthenticationDaoHasBackendFailure() {
        expect(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS)).andThrow(new DataRetrievalFailureException("This mock simulator is designed to fail"));
        replay(mockUserDetailService);

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);
        try {
            provider.authenticate(token);
            fail("Should have thrown AuthenticationServiceException");
        } catch (AuthenticationServiceException expected) {
            assertTrue(true);
        }

        verify(mockUserDetailService);
    }

    public void testAuthenticates() {
        expect(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS)).andReturn(user);
        replay(mockUserDetailService);

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);
        Authentication result = provider.authenticate(token);

        if (!(result instanceof VistaAuthenticationToken)) {
            fail("Should have returned instance of VistaAuthenticationToken");
        }
        assertNotSame(token, result);

        VistaAuthenticationToken castResult = (VistaAuthenticationToken) result;
        assertTrue(VistaUserDetails.class.isAssignableFrom(castResult.getPrincipal().getClass()));
        assertEquals(TEST_ACCESS, castResult.getAccessCode());
        assertEquals(TEST_VERIFY, castResult.getVerifyCode());
        assertEquals("ROLE_ONE", castResult.getAuthorities()[0].getAuthority());
        assertEquals("ROLE_TWO", castResult.getAuthorities()[1].getAuthority());
        assertEquals(TEST_REMOTE_ADDRESS, castResult.getDetails());

        verify(mockUserDetailService);
    }

    public void testAuthenticatesASecondTime() {
        expect(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS)).andReturn(user);
        replay(mockUserDetailService);

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);

        Authentication result = provider.authenticate(token);

        if (!(result instanceof VistaAuthenticationToken)) {
            fail("Should have returned instance of VistaAuthenticationToken");
        }

        // Now try to authenticate with the previous result (with its UserDetails)
        Authentication result2 = provider.authenticate(result);

        if (!(result2 instanceof VistaAuthenticationToken)) {
            fail("Should have returned instance of VistaAuthenticationToken");
        }

        assertNotSame(result, result2);
        assertEquals(result.getCredentials(), result2.getCredentials());
    }

    public void testDetectsNullBeingReturnedFromAuthenticationDao() {
        expect(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS)).andReturn(null);
        replay(mockUserDetailService);

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);

        try {
            provider.authenticate(token);
            fail("Should have thrown AuthenticationServiceException");
        } catch (AuthenticationServiceException expected) {
            assertEquals("VistaUserDetailsService returned null, which is an interface contract violation",
                    expected.getMessage());
        }
    }

    public void testGoesBackToAuthenticationDaoToObtainLatestVerifyCodeIfCachedVerifyCodeSeemsIncorrect() {
        expect(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS)).andReturn(user);
        replay(mockUserDetailService);

        VistaAuthenticationToken token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, TEST_VERIFY, TEST_REMOTE_ADDRESS);

        // This will work, as password still "koala"
        provider.authenticate(token);

        // Check "12345@663 = 10VEHU;VEHU10;192.168.0.1" ended up in the cache
        assertEquals(TEST_ACCESS + ";" + TEST_VERIFY + ";" + TEST_REMOTE_ADDRESS, mockCache.getUserFromCache(TEST_DUZ + "@" + TEST_STATION_NUMBER).getPassword());
        verify(mockUserDetailService);

        // Now change the password the AuthenticationDao will return
        EasyMock.reset(mockUserDetailService);
        user = createUser("54321.123456789", TEST_STATION_NUMBER, TEST_DUZ, TEST_ACCESS + ";easternLongNeckTurtle;" + TEST_REMOTE_ADDRESS, true, true, true, true, new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl("ROLE_TWO"));
        expect(mockUserDetailService.login(TEST_STATION_NUMBER, TEST_ACCESS, "easternLongNeckTurtle", TEST_REMOTE_ADDRESS)).andReturn(user);
        replay(mockUserDetailService);

        // Now try authentication again, with the new password
        token = new VistaAuthenticationToken(TEST_STATION_NUMBER, TEST_ACCESS, "easternLongNeckTurtle", TEST_REMOTE_ADDRESS);
        provider.authenticate(token);

        // To get this far, the new password was accepted
        // Check the cache was updated
        assertEquals("10VEHU;easternLongNeckTurtle;192.168.0.1", mockCache.getUserFromCache(TEST_DUZ + "@" + TEST_STATION_NUMBER).getPassword());
    }

    public void testStartupFailsIfNoVistaUserDetailsService()
            throws Exception {
        VistaAuthenticationProvider provider = new VistaAuthenticationProvider();

        try {
            provider.afterPropertiesSet();
            fail("Should have thrown IllegalArgumentException");
        } catch (IllegalArgumentException expected) {
            assertTrue(true);
        }
    }

    public void testStartupFailsIfNoUserCacheSet() throws Exception {
        VistaAuthenticationProvider provider = new VistaAuthenticationProvider();
        provider.setUserDetailsService(mockUserDetailService);
        assertEquals(NullUserCache.class, provider.getUserCache().getClass());
        provider.setUserCache(null);

        try {
            provider.afterPropertiesSet();
            fail("Should have thrown IllegalArgumentException");
        } catch (IllegalArgumentException expected) {
            assertTrue(true);
        }
    }

    public void testStartupSuccess() throws Exception {
        VistaAuthenticationProvider provider = new VistaAuthenticationProvider();
        provider.setUserDetailsService(mockUserDetailService);
        provider.setUserCache(new MockUserCache());
        assertSame(mockUserDetailService, provider.getUserDetailsService());
        provider.afterPropertiesSet();
    }

    private class MockUserCache implements UserCache {
        private Map cache = new HashMap();

        public UserDetails getUserFromCache(String username) {
            return (UserDetails) cache.get(username);
        }

        public void putUserInCache(UserDetails user) {
            cache.put(user.getUsername(), user);
        }

        public void removeUserFromCache(String username) {
        }
    }
}
*/
