Here is another installment on my second iteration of the key manager. The iteration contains a lot more code than originally anticipated, so I have to split describing it up in a couple of posts.
For today, I chose to focus on another aspect of using EasyMock: class extensions. In my original design goals, I mentioned that data to the server should always be encrypted (no clear text passwords should arrive in App Engine). To make it easier to enforce this programatically, I came up with the following classes. First, I built an abstract
Scrambler class that can make a String or a PasswordData object unreadable:package com.appenginefan.schluesselmeister.client;
/**
* Contains logic to scramble/unscramble strings and
* PasswordData.
*/
public abstract class Scrambler {
protected String scramblingSecret = "thereIsNoSpoon";
public void setScramblingSecret(String secret) {
this.scramblingSecret = secret;
}
public abstract String scramble(String s);
public abstract String unscramble(String s);
public PasswordData scramble(PasswordData p) {
p = p.clone();
p.setCategory(scramble(p.getCategory()));
p.setDescription(scramble(p.getDescription()));
p.setKey(scramble(p.getKey()));
p.setPassword(scramble(p.getPassword()));
p.setUrl(scramble(p.getUrl()));
return p;
}
public PasswordData unscramble(PasswordData p) {
p = p.clone();
p.setCategory(unscramble(p.getCategory()));
p.setDescription(unscramble(p.getDescription()));
p.setKey(unscramble(p.getKey()));
p.setPassword(unscramble(p.getPassword()));
p.setUrl(unscramble(p.getUrl()));
return p;
}
}
Then, I built a
Store class that would accept data in an encrypted fashion and release it in an unencrypted way for the UI:package com.appenginefan.schluesselmeister.client;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Contains any data structure that reside in the memory of
* the browser.
*/
public class Store {
private final Scrambler scrambler;
private String currentCategoryScrambled = "";
private final List<String> listOfCategories =
new ArrayList<String>();
private final Map<String, List<PasswordData>> passwordCache =
new HashMap<String, List<PasswordData>>();
public Store(Scrambler scrambler) {
this.scrambler = scrambler;
}
public void updateCacheWithScrambledData(
String scrambledCategory,
List<PasswordData> scrambledData) {
passwordCache.put(scrambledCategory,
new ArrayList<PasswordData>(scrambledData));
}
public void updateListOfCategoriesWithScrambledData(
List<String> newScrambledList) {
listOfCategories.clear();
listOfCategories.addAll(newScrambledList);
}
public List<String> getListOfCategoriesUnscrambled() {
List<String> result = new ArrayList<String>();
for (String s : listOfCategories) {
result.add(scrambler.unscramble(s));
}
Collections.sort(result);
return result;
}
public List<PasswordData> getListOfPasswordDataUnscrambled(
String scrambledCategoryOrNull) {
final List<PasswordData> result =
new ArrayList<PasswordData>();
if (passwordCache.containsKey(scrambledCategoryOrNull)) {
for (PasswordData data : passwordCache
.get(scrambledCategoryOrNull)) {
result.add(scrambler.unscramble(data));
}
}
return result;
}
public String getCurrentCategoryScrambled() {
return currentCategoryScrambled;
}
public void setCurrentCategory(
String unscrambledCurrentCategory) {
if (unscrambledCurrentCategory != null) {
this.currentCategoryScrambled =
scrambler.scramble(unscrambledCurrentCategory);
} else {
this.currentCategoryScrambled = null;
}
}
}
Unlike in my previous article, I have chosen not to program against interfaces.
Scrambler has abstract methods, but the code for encoding a PasswordData object is independent on the exact encryption algorithm. Store is a simple utility class, and wrapping it in an interface feels like overkill.Despite not having interfaces, I still would like to use my mocking framework because:
- I do not have to implement an encryption strategy, just to test with a
Scrambler, and - I can easily restrict what methods should be called and with what parameters.
Lucky for me, EasyMock has a class extensions package. It uses cglib to create mock objects even for existing classes. The following unit tests makes use of this by creating a mock
Scrambler:package com.appenginefan.schluesselmeister.tests;
import java.util.Collections;
import org.easymock.classextension.EasyMock;
import com.appenginefan.schluesselmeister.client.PasswordData;
import com.appenginefan.schluesselmeister.client.Scrambler;
import com.appenginefan.schluesselmeister.client.Store;
import junit.framework.TestCase;
public class StoreTest
extends TestCase {
private Scrambler scrambler;
private Store store;
private PasswordData unscrambledData = new PasswordData();
private PasswordData scrambledData = new PasswordData();
private String scrambled = "foo";
private String unscrambled = "oof";
@Override
protected void setUp() throws Exception {
super.setUp();
scrambler = EasyMock.createMock(Scrambler.class);
store = new Store(scrambler);
unscrambledData.setCategory(unscrambled);
unscrambledData.setId(1L);
scrambledData.setCategory(scrambled);
scrambledData.setId(2L);
}
public void testUpdateData() {
// Nothing should get scrambled when data gets added
EasyMock.replay(scrambler);
store.updateCacheWithScrambledData(scrambledData
.getCategory(), Collections
.singletonList(scrambledData));
EasyMock.verify(scrambler);
EasyMock.reset(scrambler);
// When we get the data out, it should be unscrambled
EasyMock.expect(scrambler.unscramble(scrambledData))
.andReturn(unscrambledData);
EasyMock.replay(scrambler);
assertEquals(unscrambledData, store
.getListOfPasswordDataUnscrambled(scrambled).get(0));
EasyMock.verify(scrambler);
}
public void testUpdateCategories() {
// Nothing should get scrambled when data gets added
EasyMock.replay(scrambler);
store
.updateListOfCategoriesWithScrambledData(Collections
.singletonList(scrambled));
EasyMock.verify(scrambler);
EasyMock.reset(scrambler);
// When we get the data out, it should be unscrambled
EasyMock.expect(scrambler.unscramble(scrambled))
.andReturn(unscrambled);
EasyMock.replay(scrambler);
assertEquals(unscrambled, store
.getListOfCategoriesUnscrambled().get(0));
EasyMock.verify(scrambler);
}
public void testUpdateCurrentCategory() {
// The category should get scrambled the moment it gets
// added
EasyMock.expect(scrambler.scramble(unscrambled))
.andReturn(scrambled);
EasyMock.replay(scrambler);
store.setCurrentCategory(unscrambled);
EasyMock.verify(scrambler);
EasyMock.reset(scrambler);
// When we get the data out, it should be scrambled
EasyMock.replay(scrambler);
assertEquals(scrambled, store
.getCurrentCategoryScrambled());
EasyMock.verify(scrambler);
}
}
Mock frameworks like EasyMock make, well..., mocking easy ;-). To reiterate: all the unit tests I am showing here are written in Java but for GTW code. This means, the classes I am testing here will eventually be compiled to JavaScript and executed on the client -- yet I can still easily test them in Eclipse and integrate those tests into whatever build infrastructure (ant, continuous build, ...) I may have.
Last but not least...
A quick shoutout to a couple of interesting pages:
- Two developers have independently gotten PHP running on App Engine. Check out Brian and Webdigi.
- A new blog dedicated to Java and Groovy and App Engine.
- App Engine Blog: Lord of the REPLS
0 comments:
Post a Comment