/* * Copyright (C) 2015 Google Inc. All Rights Reserved. * * 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 apps.provisioning.data; import java.sql.SQLException; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; import org.easymock.EasyMock; import org.easymock.IMocksControl; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import apps.provisioning.server.account.data.UsernameDataSource; import apps.provisioning.server.apis.GoogleDirectory; public class UsernameCacheTest { private final String TESTING_DB_PATH = Thread.currentThread().getContextClassLoader() .getResource(".").getPath(); private final String TESTING_DB_NAME = "testdb"; // Simulate that copying a large number of users to the DB takes a whole // second. private final int SIMULATED_COPY_USERNAMES_DELAY = 1000; private final Logger logger = Logger.getLogger(UsernameCacheTest.class.getName()); private IMocksControl control; private UsernameCache usernameCache; /** * Inner class used to fake a Google Directory with different user names for each call to copy to * a data source. This class is used instead of actually retrieving and updating usernames from a * real Google Directory. It also simulates a delay when populating the data source. * */ private class FakeGoogleDirectory extends GoogleDirectory { private ArrayList<String> usernames; public FakeGoogleDirectory(ArrayList<String> usernames) { super(); this.usernames = usernames; } @Override public void copyToDataSource(UsernameDataSource dataSource) throws Exception { dataSource.insertMultiple(usernames); Thread.sleep(SIMULATED_COPY_USERNAMES_DELAY); } } @Before public void setUp() throws Exception { control = EasyMock.createStrictControl(); } @After public void tearDown() throws Exception { // Dispose the DB if one was created. if (usernameCache != null && usernameCache.isReady()) { usernameCache.disposeDataSource(); } } /** * Tests refreshing the cache every second two times and asserts that the expected user names * after the second refresh are correct. Makes sure that the UsernameCache takes into account the * time spent when refreshing the cache. * * @throws Exception */ @Test public void testRefreshCacheTwice() throws Exception { // Refresh every second starting at second 0. int initialUpdateDelayInSeconds = 0; int updateRateInSeconds = 1; GoogleDirectory googleDirectoryMock = control.createMock(GoogleDirectory.class); // Set up the Google Directory mock to return a set of users in the first // call to populate it and a different set in the second call. ArrayList<String> usernames1 = new ArrayList<String>(); usernames1.add("dummyuser1"); usernames1.add("dummyuser2"); FakeGoogleDirectory fakeGoogleDirectory1 = new FakeGoogleDirectory(usernames1); googleDirectoryMock.copyToDataSource(EasyMock.anyObject(H2DataSource.class)); EasyMock.expectLastCall().andDelegateTo(fakeGoogleDirectory1); // Second set of users for the second call to refresh. ArrayList<String> usernames2 = new ArrayList<String>(); usernames2.add("dummyuser3"); usernames2.add("dummyuser4"); usernames2.add("dummyuser5"); FakeGoogleDirectory fakeGoogleDirectory2 = new FakeGoogleDirectory(usernames2); googleDirectoryMock.copyToDataSource(EasyMock.anyObject(H2DataSource.class)); EasyMock.expectLastCall().andDelegateTo(fakeGoogleDirectory2); control.replay(); logger.log(Level.INFO, "Testing with DB at path: " + TESTING_DB_PATH); usernameCache = new UsernameCache(initialUpdateDelayInSeconds, updateRateInSeconds, TESTING_DB_PATH, TESTING_DB_NAME, googleDirectoryMock); // We set the cache to refresh every second starting at second 0. Sleep for // 1.5 seconds so that it refreshes twice and then verify that the state is // as expected. Take into account the SIMULATED_COPY_USERNAMES_DELAY. Thread.sleep(1500 + 2 * SIMULATED_COPY_USERNAMES_DELAY); control.verify(); Assert.assertTrue(usernameCache.isReady()); // Assert that the cache was refreshed properly. // The first set should not exist anymore. for (String username : usernames1) { Assert.assertFalse(usernameCache.exists(username)); } // The second set should exist. for (String username : usernames2) { Assert.assertTrue(usernameCache.exists(username)); } } /** * Tests calling exists while the cache is being refreshed. * * @throws Exception */ @Test public void testExistsWhileRefreshing() throws Exception { // Refresh every second starting at second 0. int initialUpdateDelayInSeconds = 0; int updateRateInSeconds = 1; GoogleDirectory googleDirectoryMock = control.createMock(GoogleDirectory.class); // Set up the Google Directory mock to return a set of users in the first // call to populate it and a different set in the second call. ArrayList<String> usernames1 = new ArrayList<String>(); usernames1.add("dummyuser1"); usernames1.add("dummyuser2"); FakeGoogleDirectory fakeGoogleDirectory1 = new FakeGoogleDirectory(usernames1); googleDirectoryMock.copyToDataSource(EasyMock.anyObject(H2DataSource.class)); EasyMock.expectLastCall().andDelegateTo(fakeGoogleDirectory1); // Second set of users for the second call to refresh. ArrayList<String> usernames2 = new ArrayList<String>(); usernames2.add("dummyuser3"); usernames2.add("dummyuser4"); usernames2.add("dummyuser5"); FakeGoogleDirectory fakeGoogleDirectory2 = new FakeGoogleDirectory(usernames2); googleDirectoryMock.copyToDataSource(EasyMock.anyObject(H2DataSource.class)); EasyMock.expectLastCall().andDelegateTo(fakeGoogleDirectory2); control.replay(); logger.log(Level.INFO, "Testing with DB at path: " + TESTING_DB_PATH); usernameCache = new UsernameCache(initialUpdateDelayInSeconds, updateRateInSeconds, TESTING_DB_PATH, TESTING_DB_NAME, googleDirectoryMock); // We set the cache to refresh every second starting at second 0. Sleep for // 1.5 seconds so that we are in the process of refreshing the cache. Take // into account one SIMULATED_COPY_USERNAMES_DELAY. Thread.sleep(1500 + SIMULATED_COPY_USERNAMES_DELAY); control.verify(); // Assert that the username cache is still refreshing. Assert.assertEquals(UsernameCache.STATUS_REFRESHING, usernameCache.getStatus()); // Assert that the old cache is still read. for (String username : usernames1) { Assert.assertTrue(usernameCache.exists(username)); } // Assert that the new cache is not read yet. for (String username : usernames2) { Assert.assertFalse(usernameCache.exists(username)); } } /** * Tests that creating a username while caching inserts it when ready. * * @throws SQLException * @throws Exception */ @Test public void testInsertUserWhileCaching() throws SQLException, Exception { // Refresh every second starting at second 0. int initialUpdateDelayInSeconds = 0; int updateRateInSeconds = 1; GoogleDirectory googleDirectoryMock = control.createMock(GoogleDirectory.class); // Set up the Google Directory mock to return a set of users in the first // call to populate it and a different set in the second call. ArrayList<String> usernames1 = new ArrayList<String>(); usernames1.add("dummyuser1"); usernames1.add("dummyuser2"); FakeGoogleDirectory fakeGoogleDirectory1 = new FakeGoogleDirectory(usernames1); googleDirectoryMock.copyToDataSource(EasyMock.anyObject(H2DataSource.class)); EasyMock.expectLastCall().andDelegateTo(fakeGoogleDirectory1); control.replay(); logger.log(Level.INFO, "Testing with DB at path: " + TESTING_DB_PATH); usernameCache = new UsernameCache(initialUpdateDelayInSeconds, updateRateInSeconds, TESTING_DB_PATH, TESTING_DB_NAME, googleDirectoryMock); // Sleep for half the time it takes to refresh the cache so that we test // inserting a username while caching. Thread.sleep(SIMULATED_COPY_USERNAMES_DELAY / 2); control.verify(); // Assert that the username cache is still caching. Assert.assertEquals(UsernameCache.STATUS_CACHING, usernameCache.getStatus()); // Now insert a new username to the cache. String newUsername = "dummyuser3"; usernameCache.insert(newUsername); // Sleep again to make sure the cache is ready. Thread.sleep(500 + SIMULATED_COPY_USERNAMES_DELAY / 2); Assert.assertEquals(UsernameCache.STATUS_READY, usernameCache.getStatus()); // Assert that all the users are present, including the one added at the // middle of the caching process. for (String username : usernames1) { Assert.assertTrue(usernameCache.exists(username)); } Assert.assertTrue(usernameCache.exists(newUsername)); } /** * Tests inserting a username when the cache is ready. * * @throws SQLException * @throws Exception */ @Test public void testInsertUserWhenReady() throws SQLException, Exception { // Refresh every second starting at second 0. int initialUpdateDelayInSeconds = 0; int updateRateInSeconds = 1; GoogleDirectory googleDirectoryMock = control.createMock(GoogleDirectory.class); // Set up the Google Directory mock to return a set of users in the first // call to populate it and a different set in the second call. ArrayList<String> usernames1 = new ArrayList<String>(); usernames1.add("dummyuser1"); usernames1.add("dummyuser2"); FakeGoogleDirectory fakeGoogleDirectory1 = new FakeGoogleDirectory(usernames1); googleDirectoryMock.copyToDataSource(EasyMock.anyObject(H2DataSource.class)); EasyMock.expectLastCall().andDelegateTo(fakeGoogleDirectory1); control.replay(); logger.log(Level.INFO, "Testing with DB at path: " + TESTING_DB_PATH); usernameCache = new UsernameCache(initialUpdateDelayInSeconds, updateRateInSeconds, TESTING_DB_PATH, TESTING_DB_NAME, googleDirectoryMock); // Sleep so that the cache is ready. Thread.sleep(500 + SIMULATED_COPY_USERNAMES_DELAY); control.verify(); // Assert that the username cache is ready. Assert.assertEquals(UsernameCache.STATUS_READY, usernameCache.getStatus()); // Now insert a new username to the cache. String newUsername = "dummyuser3"; usernameCache.insert(newUsername); // Assert that all the users are present, including the one added at the // end of the caching process. for (String username : usernames1) { Assert.assertTrue(usernameCache.exists(username)); } Assert.assertTrue(usernameCache.exists(newUsername)); } /** * Tests that trying to read an unpopulated cache throws an exception. * * @throws SQLException * @throws Exception */ @Test public void testReadWhenEmptyThrowsError() throws SQLException, Exception { // Refresh every second starting at second 1. int initialUpdateDelayInSeconds = 1; int updateRateInSeconds = 1; GoogleDirectory googleDirectoryMock = control.createMock(GoogleDirectory.class); control.replay(); usernameCache = new UsernameCache(initialUpdateDelayInSeconds, updateRateInSeconds, TESTING_DB_PATH, TESTING_DB_NAME, googleDirectoryMock); control.verify(); try { usernameCache.exists("dummyuser"); Assert.fail("Should have thrown an exception when reading an unpopulated cache."); } catch (Exception e) { Assert.assertEquals("Should not call exists if the data source hasn't been created", e.getMessage()); } } /** * Tests that the status of an unpopulated cache is not ready. * * @throws Exception */ @Test public void testNotReady() throws Exception { // Refresh every second starting at second 1. int initialUpdateDelayInSeconds = 1; int updateRateInSeconds = 1; GoogleDirectory googleDirectoryMock = control.createMock(GoogleDirectory.class); control.replay(); usernameCache = new UsernameCache(initialUpdateDelayInSeconds, updateRateInSeconds, TESTING_DB_PATH, TESTING_DB_NAME, googleDirectoryMock); control.verify(); Assert.assertFalse(usernameCache.isReady()); } }