package com.trsst;
import java.io.File;
import java.io.StringReader;
import java.net.URL;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.xml.namespace.QName;
import org.apache.abdera.Abdera;
import org.apache.abdera.model.Element;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;
import org.bouncycastle.util.Arrays;
import com.google.common.io.Files;
import com.trsst.client.Client;
import com.trsst.client.EntryOptions;
import com.trsst.client.FeedOptions;
import com.trsst.server.Server;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
/**
* Unit test for command-line functions.
*/
public class TrsstTest extends TestCase {
/**
* Create the test case
*
* @param testName
* name of the test case
*/
public TrsstTest(String testName) {
super(testName);
}
/**
* @return the suite of tests being tested
*/
public static Test suite() {
return new TestSuite(TrsstTest.class);
}
/**
* Test the command line operations.
*/
public void testApp() {
try {
// System.setProperty("org.slf4j.simpleLogger.defaultLogLevel",
// "debug");
Feed feed;
Entry entry;
// write test files to temp directory
File tmp = Files.createTempDir();
tmp.deleteOnExit();
URL serviceURL = null;
if (System.getProperty("com.trsst.TrsstTest.server") == null) {
// start a local server for testing
System.setProperty("com.trsst.server.storage",
tmp.getAbsolutePath());
Server server = new Server();
serviceURL = server.getServiceURL();
} else {
serviceURL = new URL(
System.getProperty("com.trsst.TrsstTest.server"));
}
// serviceURL = new URL("http://localhost:8888/trsst");
assertNotNull(serviceURL);
String feedId;
Client client = new Client(serviceURL);
KeyPair signingKeys, encryptionKeys;
PublicKey publicKey;
Element signatureElement;
String signatureValue;
// generate account
signingKeys = Common.generateSigningKeyPair();
assertNotNull("Generating signing keys", signingKeys);
encryptionKeys = Common.generateEncryptionKeyPair();
assertNotNull("Generating encryption keys", encryptionKeys);
feedId = Common.toFeedId(signingKeys.getPublic());
// public key serialization
publicKey = signingKeys.getPublic();
assertEquals("Signing keys serialize",
Common.toX509FromPublicKey(publicKey),
Common.toX509FromPublicKey(Common
.toPublicKeyFromX509(Common
.toX509FromPublicKey(publicKey))));
publicKey = encryptionKeys.getPublic();
assertEquals("Encryption keys serialize",
Common.toX509FromPublicKey(publicKey),
Common.toX509FromPublicKey(Common
.toPublicKeyFromX509(Common
.toX509FromPublicKey(publicKey))));
// generate feed with no entries
feed = client.post(signingKeys, encryptionKeys, new EntryOptions(),
new FeedOptions());
assertNotNull("Generating empty feed", feed);
assertEquals("Empty feed retains id",
Common.fromFeedUrn(feed.getId()), feedId);
assertEquals("Empty feed contains no entries", feed.getEntries()
.size(), 0);
signatureElement = feed.getFirstChild(new QName(
"http://www.w3.org/2000/09/xmldsig#", "Signature"));
assertNotNull("Feed has signature", signatureElement);
// verify string serialization roundtrip
String raw = feed.toString();
feed = (Feed) Abdera.getInstance().getParser()
.parse(new StringReader(raw)).getRoot();
feed = client.push(feed, serviceURL);
assertNotNull("String serialization", feed);
assertEquals("Serialized feed retains id",
Common.fromFeedUrn(feed.getId()), feedId);
assertEquals("Serialized feed contains no entries", feed
.getEntries().size(), 0);
signatureElement = feed.getFirstChild(new QName(
"http://www.w3.org/2000/09/xmldsig#", "Signature"));
assertNotNull("Serialized feed has signature", signatureElement);
// generate account
signingKeys = Common.generateSigningKeyPair();
assertNotNull("Generating signing keys", signingKeys);
encryptionKeys = Common.generateEncryptionKeyPair();
assertNotNull("Generating encryption keys", encryptionKeys);
feedId = Common.toFeedId(signingKeys.getPublic());
// generate feed with entry
feed = client.post(signingKeys, encryptionKeys,
new EntryOptions().setStatus("First Post!"),
new FeedOptions());
assertNotNull("Generating feed with entry", feed);
assertEquals("Feed retains id", feedId,
Common.fromFeedUrn(feed.getId()));
assertEquals("Feed contains one entry", 1, feed.getEntries().size());
signatureElement = feed.getFirstChild(new QName(
"http://www.w3.org/2000/09/xmldsig#", "Signature"));
assertNotNull("Feed has signature", signatureElement);
entry = feed.getEntries().get(0);
assertEquals("Entry retains title", "First Post!", entry.getTitle());
// verify string serialization with-entry roundtrip
raw = feed.toString();
feed = (Feed) Abdera.getInstance().getParser()
.parse(new StringReader(raw)).getRoot();
feed = client.push(feed, serviceURL);
assertNotNull("String serialization with entry", feed);
assertEquals("Serialized feed with entry retains id",
Common.fromFeedUrn(feed.getId()), feedId);
assertEquals("Serialized feed contains one entry", 1, feed
.getEntries().size());
signatureElement = feed.getFirstChild(new QName(
"http://www.w3.org/2000/09/xmldsig#", "Signature"));
assertNotNull("Serialized feed with entry has signature",
signatureElement);
// verify edited string fails to validate
raw = feed.toString();
raw = raw.replace("First", "Second");
feed = (Feed) Abdera.getInstance().getParser()
.parse(new StringReader(raw)).getRoot();
// this generates some errors on the console but it's ok
try {
feed = null;
feed = client.push(feed, serviceURL);
} catch (Throwable t) {
// ignore: this is the exception we want to test
}
assertNull("Verification fails with edited entry", feed);
// encryption roundtrip
byte[] key = Crypto.generateAESKey();
long timestamp = System.currentTimeMillis();
KeyPair a = Common.generateEncryptionKeyPair();
KeyPair b = Common.generateEncryptionKeyPair();
byte[] bytes = Crypto.encryptKeyWithIES(key, timestamp,
a.getPublic(), b.getPrivate());
byte[] decrypted = Crypto.decryptKeyWithIES(bytes, timestamp,
b.getPublic(), a.getPrivate());
assertTrue("ECDH Encryption round trip test",
Arrays.areEqual(key, decrypted));
// serialized entry encryption roundtrip
String test = entry.toString();
bytes = Client.encryptElementAES(entry, key);
Element element = Client.decryptElementAES(bytes, key);
assertEquals("AES Encryption round trip test", test,
element.toString());
// grab signature
signatureElement = entry.getFirstChild(new QName(
"http://www.w3.org/2000/09/xmldsig#", "Signature"));
assertNotNull("Entry has signature", signatureElement);
signatureElement = signatureElement.getFirstChild(new QName(
"http://www.w3.org/2000/09/xmldsig#", "SignatureValue"));
assertNotNull("Entry has signature value", signatureElement);
// save for predecessor verification
String predecessorId = entry.getId().toString();
signatureValue = signatureElement.getText();
// generate entry with full options
feed = client.post(
signingKeys,
encryptionKeys,
new EntryOptions()
.setStatus("你好,世界。")
.setVerb("post")
.setBody("This is the body")
.setMentions(new String[] { feedId, feedId })
.setTags(
new String[] { "fitter", "happier",
"more productive" }),
new FeedOptions());
assertNotNull("Generating second entry", feed);
assertEquals("Feed contains one entry", 1, feed.getEntries().size());
entry = feed.getEntries().get(0);
assertEquals("Entry retains title with unicode", "你好,世界。",
entry.getTitle());
assertEquals("Entry contains verb", "post",
entry.getSimpleExtension(new QName(
"http://activitystrea.ms/spec/1.0/", "verb",
"activity")));
assertEquals("Feed retains body", "This is the body",
entry.getSummary());
// verify predecessor signature
signatureElement = entry.getFirstChild(new QName(
"http://www.w3.org/2000/09/xmldsig#", "Signature"));
assertNotNull("Entry has signature", signatureElement);
signatureElement = signatureElement.getFirstChild(new QName(
"http://www.w3.org/2000/09/xmldsig#", "SignatureValue"));
assertNotNull("Entry has signature value", signatureElement);
signatureElement = entry.getFirstChild(new QName(Common.NS_URI,
Common.PREDECESSOR));
assertNotNull("Entry has predecessor", signatureElement);
assertEquals("Entry predecessor id matches", predecessorId,
signatureElement.getAttributeValue("id"));
assertEquals("Entry predecessor signature matches", signatureValue,
signatureElement.getText());
// pull both entries
feed = client.pull(feedId);
assertEquals("Feed contains two entries", 2, feed.getEntries()
.size());
entry = feed.getEntries().get(0);
// mark both entries as deleted
String firstIdToDelete = feed.getEntries().get(0).getId()
.toString();
String secondIdToDelete = feed.getEntries().get(1).getId()
.toString();
feed = client
.post(signingKeys,
encryptionKeys,
new EntryOptions().setVerb("delete").setMentions(
new String[] { firstIdToDelete,
secondIdToDelete }),
new FeedOptions());
assertNotNull("Delete operation succeeded", feed);
entry = feed.getEntries().get(0);
feed = client.pull(firstIdToDelete);
entry = feed.getEntries().get(0);
assertEquals("First entry was deleted", "deleted",
entry.getSimpleExtension(new QName(
"http://activitystrea.ms/spec/1.0/", "verb")));
feed = client.pull(secondIdToDelete);
entry = feed.getEntries().get(0);
assertEquals("First entry was deleted", "deleted",
entry.getSimpleExtension(new QName(
"http://activitystrea.ms/spec/1.0/", "verb")));
// file storage date granularity is currently too large for this
// test to work
// assertEquals("Feed lists most recent entry first",
// "Second Post!", entry.getTitle() );
// make sure we're retaining all entries
for (int i = 0; i < 2; i++) {
feed = client.post(
signingKeys,
encryptionKeys,
new EntryOptions()
.setStatus("Multipost!")
.setVerb("post")
.setBody("This is the body")
.setMentions(new String[] { feedId })
.setTags(
new String[] { "fitter", "happier",
"more productive" }),
new FeedOptions());
entry = feed.getEntries().get(0);
}
feed = client.pull(Common.fromFeedUrn(feed.getId()));
assertTrue("Feed has all entries", (5 == feed.getEntries().size()));
// make sure server is paginating
for (int i = 0; i < 5; i++) {
feed = client.post(
signingKeys,
encryptionKeys,
new EntryOptions()
.setStatus("Multipost!")
.setVerb("post")
.setBody("This is the body")
.setMentions(new String[] { feedId })
.setTags(
new String[] { "fitter", "happier",
"more productive" }),
new FeedOptions());
entry = feed.getEntries().get(0);
}
feed = client.pull(Common.fromFeedUrn(feed.getId()) + "?count=3");
assertTrue("Feed has only first page of entries", (3 == feed
.getEntries().size()));
// test pull of a single entry
long existingId = Common.toEntryId(entry.getId());
feed = client.pull(entry.getId().toString());
assertNotNull("Single entry feed result", feed);
assertEquals("Single entry feed retains id", feedId,
Common.fromFeedUrn(feed.getId()));
assertEquals("Single entry feed contains one entry", 1, feed
.getEntries().size());
signatureElement = feed.getFirstChild(new QName(
"http://www.w3.org/2000/09/xmldsig#", "Signature"));
assertNotNull("Single entry feed has signature", signatureElement);
entry = feed.getEntries().get(0);
assertEquals("Single entry retains id", existingId,
Common.toEntryId(entry.getId()));
// generate recipient keys
KeyPair recipientKeys = Common.generateEncryptionKeyPair();
// generate encrypted entry
feed = client
.post(signingKeys,
encryptionKeys,
new EntryOptions()
.setStatus("This is the encrypted entry")
.setBody("This is the encrypted body")
.setContentUrl("http://www.trsst.com")
.encryptFor(
new String[] { feed.getId()
.toString() },
new EntryOptions()
.setMentions(
new String[] {
"182pdh1P6be28uHCUpfZnrQ5M7AJcuXLX2",
"1JMcxLznMbyDYerWo3WUpTPhbwjFq97MeZ" })
.setTags(
new String[] {
"public",
"unencrypted",
"in the clear" })
.setStatus(
"Unencrypted title with encrypted entry")),
new FeedOptions());
entry = feed.getEntries().get(0);
// pull and decrypt the entry
feed = client.pull(entry.getId().toString(),
new PrivateKey[] { encryptionKeys.getPrivate() });
assertNotNull("Generated encrypted entry", feed);
entry = feed.getEntries().get(0);
assertFalse("Entry does not retain status",
"This is the encrypted entry".equals(entry.getTitle()));
assertFalse("Entry does not retain body",
"This is the encrypted body".equals(entry.getSummary()));
assertEquals("Entry retains tags", 3, entry.getCategories().size());
assertEquals(
"Entry retains mentions",
2,
entry.getExtensions(
new QName(Common.NS_URI, "mention", "trsst"))
.size());
Element contentElement = entry.getContentElement();
assertTrue("Decoded element is an Entry",
contentElement.getFirstChild() instanceof Entry);
Entry decoded = (Entry) contentElement.getFirstChild();
assertTrue("Decoded entry retains status",
"This is the encrypted entry".equals(decoded.getTitle()));
assertTrue("Decoded entry retains body",
"This is the encrypted body".equals(decoded.getSummary()));
assertTrue("Decoded entry retains content",
"http://www.trsst.com".equals(decoded.getContentSrc()
.toString()));
// write and read the keypair
Command.writeEncryptionKeyPair(recipientKeys, "tmp", new File(tmp,
"keytest.p12"), new char[] { 'p' });
recipientKeys = Command.readEncryptionKeyPair("tmp", new File(tmp,
"keytest.p12"), new char[] { 'p' });
assertNotNull("Write and read keys from keystore", recipientKeys);
// test push to second server
// TODO: need to implement server sync test here
// Server alternateServer = new Server();
// URL alternateUrl = alternateServer.getServiceURL();
// assertNotNull(client.push(feedId, alternateUrl));
} catch (Throwable t) {
t.printStackTrace();
fail();
}
}
}