2013-08-19 15:13:32 -04:00
define ( function ( require ) {
'use strict' ;
var KeychainDAO = require ( 'js/dao/keychain-dao' ) ,
EmailDAO = require ( 'js/dao/email-dao' ) ,
2013-09-26 07:26:57 -04:00
DeviceStorageDAO = require ( 'js/dao/devicestorage-dao' ) ,
2013-09-19 12:35:12 -04:00
SmtpClient = require ( 'smtp-client' ) ,
ImapClient = require ( 'imap-client' ) ,
2013-10-11 21:19:01 -04:00
PGP = require ( 'js/crypto/pgp' ) ,
2013-08-19 15:13:32 -04:00
app = require ( 'js/app-config' ) ,
expect = chai . expect ;
var emaildaoTest = {
user : "whiteout.test@t-online.de" ,
passphrase : 'asdf' ,
asymKeySize : 512
} ;
2013-08-28 08:12:39 -04:00
var dummyMail ;
var publicKey = "-----BEGIN PUBLIC KEY-----\r\n" + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxy+Te5dyeWd7g0P+8LNO7fZDQ\r\n" + "g96xTb1J6pYE/pPTMlqhB6BRItIYjZ1US5q2vk5Zk/5KasBHAc9RbCqvh9v4XFEY\r\n" + "JVmTXC4p8ft1LYuNWIaDk+R3dyYXmRNct/JC4tks2+8fD3aOvpt0WNn3R75/FGBt\r\n" + "h4BgojAXDE+PRQtcVQIDAQAB\r\n" + "-----END PUBLIC KEY-----" ;
2013-08-20 07:30:35 -04:00
2013-08-19 15:13:32 -04:00
describe ( 'Email DAO unit tests' , function ( ) {
var emailDao , account ,
2013-10-11 21:19:01 -04:00
keychainStub , imapClientStub , smtpClientStub , pgpStub , devicestorageStub ;
2013-08-19 15:13:32 -04:00
beforeEach ( function ( ) {
2013-09-26 07:26:57 -04:00
// init dummy object
2013-08-28 08:12:39 -04:00
dummyMail = {
from : [ {
name : 'Whiteout Test' ,
address : 'whiteout.test@t-online.de'
} ] , // sender address
to : [ {
address : 'safewithme.testuser@gmail.com'
} ] , // list of receivers
subject : "Hello" , // Subject line
body : "Hello world" // plaintext body
} ;
2013-08-20 07:30:35 -04:00
account = {
2013-08-19 15:13:32 -04:00
emailAddress : emaildaoTest . user ,
symKeySize : app . config . symKeySize ,
symIvSize : app . config . symIvSize ,
asymKeySize : emaildaoTest . asymKeySize
2013-08-20 07:30:35 -04:00
} ;
2013-08-19 15:13:32 -04:00
keychainStub = sinon . createStubInstance ( KeychainDAO ) ;
imapClientStub = sinon . createStubInstance ( ImapClient ) ;
smtpClientStub = sinon . createStubInstance ( SmtpClient ) ;
2013-10-11 21:19:01 -04:00
pgpStub = sinon . createStubInstance ( PGP ) ;
2013-09-26 07:26:57 -04:00
devicestorageStub = sinon . createStubInstance ( DeviceStorageDAO ) ;
2013-08-19 15:13:32 -04:00
2013-10-11 21:19:01 -04:00
emailDao = new EmailDAO ( keychainStub , imapClientStub , smtpClientStub , pgpStub , devicestorageStub ) ;
2013-08-19 15:13:32 -04:00
} ) ;
afterEach ( function ( ) { } ) ;
describe ( 'init' , function ( ) {
it ( 'should fail due to error in getUserKeyPair' , function ( done ) {
2013-09-26 07:26:57 -04:00
devicestorageStub . init . yields ( ) ;
2013-08-19 15:13:32 -04:00
keychainStub . getUserKeyPair . yields ( 42 ) ;
emailDao . init ( account , emaildaoTest . passphrase , function ( err ) {
2013-09-26 07:26:57 -04:00
expect ( devicestorageStub . init . calledOnce ) . to . be . true ;
2013-08-19 15:13:32 -04:00
expect ( err ) . to . equal ( 42 ) ;
done ( ) ;
} ) ;
} ) ;
2013-08-20 07:30:35 -04:00
it ( 'should init with new keygen' , function ( done ) {
2013-09-26 07:26:57 -04:00
devicestorageStub . init . yields ( ) ;
2013-08-19 15:13:32 -04:00
keychainStub . getUserKeyPair . yields ( ) ;
2013-10-11 21:19:01 -04:00
pgpStub . generateKeys . yields ( null , { } ) ;
2013-10-13 13:04:49 -04:00
pgpStub . importKeys . yields ( ) ;
2013-08-19 15:13:32 -04:00
keychainStub . putUserKeyPair . yields ( ) ;
emailDao . init ( account , emaildaoTest . passphrase , function ( err ) {
2013-09-26 07:26:57 -04:00
expect ( devicestorageStub . init . calledOnce ) . to . be . true ;
2013-08-19 15:13:32 -04:00
expect ( keychainStub . getUserKeyPair . calledOnce ) . to . be . true ;
2013-10-11 21:19:01 -04:00
expect ( pgpStub . generateKeys . calledOnce ) . to . be . true ;
2013-10-13 13:04:49 -04:00
expect ( pgpStub . importKeys . calledOnce ) . to . be . true ;
2013-08-19 15:13:32 -04:00
expect ( keychainStub . putUserKeyPair . calledOnce ) . to . be . true ;
expect ( err ) . to . not . exist ;
done ( ) ;
} ) ;
} ) ;
2013-10-11 21:19:01 -04:00
it ( 'should init with stored keygen' , function ( done ) {
devicestorageStub . init . yields ( ) ;
keychainStub . getUserKeyPair . yields ( null , {
publicKey : {
_id : 'keyId' ,
userId : emaildaoTest . user ,
publicKey : 'publicKeyArmored'
} ,
privateKey : {
_id : 'keyId' ,
userId : emaildaoTest . user ,
encryptedKey : 'privateKeyArmored'
}
} ) ;
pgpStub . importKeys . yields ( ) ;
emailDao . init ( account , emaildaoTest . passphrase , function ( err ) {
expect ( devicestorageStub . init . calledOnce ) . to . be . true ;
expect ( keychainStub . getUserKeyPair . calledOnce ) . to . be . true ;
expect ( pgpStub . importKeys . calledOnce ) . to . be . true ;
expect ( err ) . to . not . exist ;
done ( ) ;
} ) ;
} ) ;
2013-08-19 15:13:32 -04:00
} ) ;
2013-09-26 11:37:56 -04:00
describe ( 'login' , function ( ) {
it ( 'should fail due to error in imap login' , function ( done ) {
imapClientStub . login . yields ( 42 ) ;
emailDao . imapLogin ( function ( err ) {
expect ( err ) . to . equal ( 42 ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should work' , function ( done ) {
imapClientStub . login . yields ( ) ;
emailDao . imapLogin ( function ( err ) {
expect ( err ) . to . not . exist ;
done ( ) ;
} ) ;
} ) ;
} ) ;
2013-08-20 07:30:35 -04:00
describe ( 'IMAP/SMTP tests' , function ( ) {
beforeEach ( function ( done ) {
2013-09-26 07:26:57 -04:00
devicestorageStub . init . yields ( ) ;
2013-08-20 07:30:35 -04:00
keychainStub . getUserKeyPair . yields ( ) ;
2013-10-11 21:19:01 -04:00
pgpStub . generateKeys . yields ( null , { } ) ;
2013-10-13 13:04:49 -04:00
pgpStub . importKeys . yields ( ) ;
2013-08-20 07:30:35 -04:00
keychainStub . putUserKeyPair . yields ( ) ;
emailDao . init ( account , emaildaoTest . passphrase , function ( err ) {
2013-09-26 07:26:57 -04:00
expect ( devicestorageStub . init . calledOnce ) . to . be . true ;
2013-08-20 07:30:35 -04:00
expect ( keychainStub . getUserKeyPair . calledOnce ) . to . be . true ;
2013-10-11 21:19:01 -04:00
expect ( pgpStub . generateKeys . calledOnce ) . to . be . true ;
2013-10-13 13:04:49 -04:00
expect ( pgpStub . importKeys . calledOnce ) . to . be . true ;
2013-08-20 07:30:35 -04:00
expect ( keychainStub . putUserKeyPair . calledOnce ) . to . be . true ;
expect ( err ) . to . not . exist ;
done ( ) ;
} ) ;
} ) ;
afterEach ( function ( done ) {
imapClientStub . logout . yields ( ) ;
emailDao . destroy ( function ( err ) {
expect ( imapClientStub . logout . calledOnce ) . to . be . true ;
expect ( err ) . to . not . exist ;
done ( ) ;
} ) ;
} ) ;
2013-08-21 07:43:19 -04:00
describe ( 'SMTP: send email' , function ( ) {
2013-08-27 13:04:26 -04:00
it ( 'should fail due to bad input' , function ( done ) {
2013-08-20 11:22:08 -04:00
emailDao . smtpSend ( { } , function ( err ) {
expect ( smtpClientStub . send . called ) . to . be . false ;
2013-08-27 13:04:26 -04:00
expect ( keychainStub . getReveiverPublicKey . called ) . to . be . false ;
expect ( err ) . to . exist ;
done ( ) ;
} ) ;
} ) ;
2013-08-28 08:12:39 -04:00
2013-08-27 13:04:26 -04:00
it ( 'should fail due to invalid email address input' , function ( done ) {
2013-08-28 08:12:39 -04:00
dummyMail . to = [ {
address : 'asfd'
} ] ;
emailDao . smtpSend ( dummyMail , function ( err ) {
2013-08-27 13:04:26 -04:00
expect ( smtpClientStub . send . called ) . to . be . false ;
expect ( keychainStub . getReveiverPublicKey . called ) . to . be . false ;
2013-08-20 11:22:08 -04:00
expect ( err ) . to . exist ;
done ( ) ;
} ) ;
} ) ;
2013-08-31 11:13:08 -04:00
it ( 'should work for a new user' , function ( done ) {
keychainStub . getReveiverPublicKey . yields ( null , null ) ;
smtpClientStub . send . yields ( ) ;
emailDao . smtpSend ( dummyMail , function ( err ) {
expect ( keychainStub . getReveiverPublicKey . calledOnce ) . to . be . true ;
2013-09-26 07:26:57 -04:00
// expect(smtpClientStub.send.called).to.be.true;
// smtpClientStub.send.calledWith(sinon.match(function(o) {
// return typeof o.attachments === 'undefined';
// }));
expect ( err ) . to . exist ;
2013-08-31 11:13:08 -04:00
done ( ) ;
} ) ;
} ) ;
2013-08-28 08:12:39 -04:00
it ( 'should work without attachments' , function ( done ) {
keychainStub . getReveiverPublicKey . yields ( null , {
_id : "fcf8b4aa-5d09-4089-8b4f-e3bc5091daf3" ,
userId : "safewithme.testuser@gmail.com" ,
publicKey : publicKey
} ) ;
2013-10-11 21:19:01 -04:00
pgpStub . exportKeys . yields ( null , { } ) ;
pgpStub . encrypt . yields ( null , 'asdfasfd' ) ;
2013-08-28 08:12:39 -04:00
smtpClientStub . send . yields ( ) ;
emailDao . smtpSend ( dummyMail , function ( err ) {
expect ( keychainStub . getReveiverPublicKey . calledOnce ) . to . be . true ;
2013-10-11 21:19:01 -04:00
expect ( pgpStub . exportKeys . calledOnce ) . to . be . true ;
expect ( pgpStub . encrypt . calledOnce ) . to . be . true ;
2013-08-28 08:12:39 -04:00
expect ( smtpClientStub . send . calledOnce ) . to . be . true ;
smtpClientStub . send . calledWith ( sinon . match ( function ( o ) {
return typeof o . attachments === 'undefined' ;
} ) ) ;
expect ( err ) . to . not . exist ;
done ( ) ;
} ) ;
} ) ;
2013-08-27 13:04:26 -04:00
2013-08-28 08:12:39 -04:00
it ( 'should work with attachments' , function ( done ) {
dummyMail . attachments = [ {
fileName : 'bar.txt' ,
contentType : 'text/plain' ,
binStr : 'barbarbarbarbar'
} ] ;
2013-08-27 13:04:26 -04:00
keychainStub . getReveiverPublicKey . yields ( null , {
_id : "fcf8b4aa-5d09-4089-8b4f-e3bc5091daf3" ,
userId : "safewithme.testuser@gmail.com" ,
publicKey : publicKey
} ) ;
2013-10-11 21:19:01 -04:00
pgpStub . exportKeys . yields ( null , { } ) ;
pgpStub . encrypt . yields ( null , 'asdfasfd' ) ;
2013-08-20 11:22:08 -04:00
smtpClientStub . send . yields ( ) ;
2013-08-27 13:04:26 -04:00
2013-08-20 11:22:08 -04:00
emailDao . smtpSend ( dummyMail , function ( err ) {
2013-08-27 13:04:26 -04:00
expect ( keychainStub . getReveiverPublicKey . calledOnce ) . to . be . true ;
2013-10-11 21:19:01 -04:00
expect ( pgpStub . exportKeys . calledOnce ) . to . be . true ;
expect ( pgpStub . encrypt . calledOnce ) . to . be . true ;
2013-08-20 11:22:08 -04:00
expect ( smtpClientStub . send . calledOnce ) . to . be . true ;
2013-08-28 08:12:39 -04:00
smtpClientStub . send . calledWith ( sinon . match ( function ( o ) {
var ptAt = dummyMail . attachments [ 0 ] ;
var ctAt = o . attachments [ 0 ] ;
return ctAt . uint8Array && ! ctAt . binStr && ctAt . fileName && ctAt . fileName !== ptAt . fileName ;
} ) ) ;
2013-08-20 11:22:08 -04:00
expect ( err ) . to . not . exist ;
done ( ) ;
} ) ;
2013-08-20 07:30:35 -04:00
} ) ;
} ) ;
2013-08-21 07:43:19 -04:00
describe ( 'IMAP: list folders' , function ( ) {
2013-10-10 13:15:16 -04:00
var dummyFolders = [ {
type : 'Inbox' ,
path : 'INBOX'
} , {
type : 'Outbox' ,
path : 'OUTBOX'
} ] ;
it ( 'should work on empty local db' , function ( done ) {
devicestorageStub . listItems . yields ( null , [ dummyFolders ] ) ;
emailDao . imapListFolders ( function ( err , folders ) {
expect ( err ) . to . not . exist ;
expect ( devicestorageStub . listItems . calledOnce ) . to . be . true ;
expect ( folders [ 0 ] . type ) . to . equal ( 'Inbox' ) ;
done ( ) ;
} ) ;
} ) ;
it ( 'should work with local cache' , function ( done ) {
devicestorageStub . listItems . yields ( null , [ ] ) ;
imapClientStub . listWellKnownFolders . yields ( null , {
inbox : dummyFolders [ 0 ]
} ) ;
devicestorageStub . storeList . yields ( ) ;
emailDao . imapListFolders ( function ( err , folders ) {
2013-09-20 12:44:14 -04:00
expect ( err ) . to . not . exist ;
2013-10-10 13:15:16 -04:00
expect ( devicestorageStub . listItems . calledOnce ) . to . be . true ;
expect ( imapClientStub . listWellKnownFolders . calledOnce ) . to . be . true ;
expect ( devicestorageStub . storeList . calledOnce ) . to . be . true ;
expect ( folders [ 0 ] . type ) . to . equal ( 'Inbox' ) ;
2013-09-20 12:44:14 -04:00
done ( ) ;
} ) ;
} ) ;
} ) ;
2013-10-04 11:02:27 -04:00
describe ( 'IMAP: get unread message count for folder' , function ( ) {
2013-09-20 12:44:14 -04:00
it ( 'should work' , function ( done ) {
imapClientStub . unreadMessages . yields ( ) ;
emailDao . unreadMessages ( function ( err ) {
expect ( imapClientStub . unreadMessages . calledOnce ) . to . be . true ;
2013-08-20 11:22:08 -04:00
expect ( err ) . to . not . exist ;
done ( ) ;
} ) ;
2013-08-20 07:30:35 -04:00
} ) ;
} ) ;
2013-08-20 13:48:49 -04:00
2013-08-21 07:43:19 -04:00
describe ( 'IMAP: list messages from folder' , function ( ) {
2013-08-20 13:48:49 -04:00
it ( 'should fail due to bad options' , function ( done ) {
emailDao . imapListMessages ( { } , function ( err ) {
expect ( imapClientStub . listMessages . called ) . to . be . false ;
expect ( err ) . to . exist ;
done ( ) ;
} ) ;
} ) ;
it ( 'should work' , function ( done ) {
imapClientStub . listMessages . yields ( ) ;
emailDao . imapListMessages ( {
folder : 'INBOX' ,
offset : 0 ,
num : 10
} , function ( err ) {
expect ( imapClientStub . listMessages . calledOnce ) . to . be . true ;
expect ( err ) . to . not . exist ;
done ( ) ;
} ) ;
} ) ;
} ) ;
2013-08-21 07:43:19 -04:00
2013-10-04 11:21:09 -04:00
describe ( 'IMAP: get message preview' , function ( ) {
2013-08-21 07:43:19 -04:00
it ( 'should fail due to bad options' , function ( done ) {
emailDao . imapGetMessage ( {
folder : 'INBOX'
} , function ( err ) {
2013-10-04 11:21:09 -04:00
expect ( imapClientStub . getMessagePreview . called ) . to . be . false ;
2013-08-21 07:43:19 -04:00
expect ( err ) . to . exist ;
done ( ) ;
} ) ;
} ) ;
2013-08-21 10:07:59 -04:00
it ( 'should parse message body without attachement' , function ( done ) {
var uid = 415 ;
2013-10-04 11:21:09 -04:00
imapClientStub . getMessagePreview . yields ( null , {
2013-08-28 13:20:59 -04:00
uid : uid ,
body : ''
2013-08-21 10:07:59 -04:00
} ) ;
emailDao . imapGetMessage ( {
folder : 'INBOX' ,
uid : uid
} , function ( err , message ) {
2013-10-04 11:21:09 -04:00
expect ( imapClientStub . getMessagePreview . calledOnce ) . to . be . true ;
2013-08-21 10:07:59 -04:00
expect ( err ) . to . not . exist ;
expect ( message . uid ) . to . equal ( uid ) ;
expect ( message . attachments ) . to . not . exist ;
done ( ) ;
} ) ;
} ) ;
2013-08-28 14:21:15 -04:00
// it('should parse message body and attachement', function(done) {
// var uid = 415,
// newImapClientStub = {
// getMessage: function() {}
// };
// sinon.stub(newImapClientStub, 'getMessage', function(options) {
// options.onMessageBody(null, {
// uid: uid,
// body: '',
// attachments: ['file.txt']
// });
// options.onAttachment(null, {
// uint8Array: new Uint8Array(42)
// });
// });
// emailDao._imapClient = newImapClientStub;
// emailDao.imapGetMessage({
// folder: 'INBOX',
// uid: uid
// }, function(err, message) {
2013-10-04 11:21:09 -04:00
// expect(newImapClientStub.getMessagePreview.calledOnce).to.be.true;
2013-08-28 14:21:15 -04:00
// expect(err).to.not.exist;
// expect(message.uid).to.equal(uid);
// expect(message.attachments[0].uint8Array).to.exist;
// emailDao._imapClient = imapClientStub;
// done();
// });
// });
2013-08-21 07:43:19 -04:00
} ) ;
2013-09-26 07:26:57 -04:00
describe ( 'IMAP: sync messages to local storage' , function ( ) {
2013-09-28 13:04:15 -04:00
it ( 'should not list unencrypted messages' , function ( done ) {
imapClientStub . listMessages . yields ( null , [ {
uid : 413 ,
} , {
uid : 414 ,
} ] ) ;
2013-10-04 11:21:09 -04:00
imapClientStub . getMessagePreview . yields ( null , {
2013-09-28 13:04:15 -04:00
body : 'asdf'
} ) ;
devicestorageStub . removeList . yields ( ) ;
devicestorageStub . storeList . yields ( ) ;
emailDao . imapSync ( {
folder : 'INBOX' ,
offset : 0 ,
num : 2
} , function ( err ) {
expect ( err ) . to . not . exist ;
expect ( imapClientStub . listMessages . calledOnce ) . to . be . true ;
2013-10-04 11:21:09 -04:00
expect ( imapClientStub . getMessagePreview . called ) . to . be . false ;
2013-09-28 13:04:15 -04:00
expect ( devicestorageStub . removeList . calledOnce ) . to . be . true ;
expect ( devicestorageStub . storeList . calledOnce ) . to . be . true ;
done ( ) ;
} ) ;
} ) ;
2013-09-26 07:26:57 -04:00
it ( 'should work' , function ( done ) {
imapClientStub . listMessages . yields ( null , [ {
uid : 413 ,
2013-09-28 13:04:15 -04:00
subject : app . string . subject
2013-09-26 07:26:57 -04:00
} , {
uid : 414 ,
2013-09-28 13:04:15 -04:00
subject : app . string . subject
2013-09-26 07:26:57 -04:00
} ] ) ;
2013-10-04 11:21:09 -04:00
imapClientStub . getMessagePreview . yields ( null , {
2013-09-26 07:26:57 -04:00
body : 'asdf'
} ) ;
2013-09-28 13:04:15 -04:00
devicestorageStub . removeList . yields ( ) ;
2013-09-26 09:48:32 -04:00
devicestorageStub . storeList . yields ( ) ;
2013-09-26 07:26:57 -04:00
emailDao . imapSync ( {
folder : 'INBOX' ,
offset : 0 ,
num : 2
} , function ( err ) {
expect ( err ) . to . not . exist ;
expect ( imapClientStub . listMessages . calledOnce ) . to . be . true ;
2013-10-04 11:21:09 -04:00
expect ( imapClientStub . getMessagePreview . calledTwice ) . to . be . true ;
2013-09-28 13:04:15 -04:00
expect ( devicestorageStub . removeList . calledOnce ) . to . be . true ;
2013-09-26 09:48:32 -04:00
expect ( devicestorageStub . storeList . calledOnce ) . to . be . true ;
2013-09-26 07:26:57 -04:00
done ( ) ;
} ) ;
} ) ;
} ) ;
describe ( 'IMAP: list messages from local storage' , function ( ) {
it ( 'should work' , function ( done ) {
2013-10-11 21:19:01 -04:00
dummyMail . body = app . string . cryptPrefix + btoa ( 'asdf' ) + app . string . cryptSuffix ;
devicestorageStub . listItems . yields ( null , [ dummyMail , dummyMail ] ) ;
keychainStub . getReveiverPublicKey . yields ( null , {
2013-09-26 07:26:57 -04:00
_id : "fcf8b4aa-5d09-4089-8b4f-e3bc5091daf3" ,
userId : "safewithme.testuser@gmail.com" ,
publicKey : publicKey
2013-10-11 21:19:01 -04:00
} ) ;
pgpStub . decrypt . yields ( null , JSON . stringify ( {
2013-10-04 09:47:30 -04:00
body : 'test body' ,
subject : 'test subject'
2013-10-11 21:19:01 -04:00
} ) ) ;
2013-09-26 07:26:57 -04:00
emailDao . listMessages ( {
folder : 'INBOX' ,
offset : 0 ,
num : 2
} , function ( err , emails ) {
2013-09-26 09:48:32 -04:00
expect ( devicestorageStub . listItems . calledOnce ) . to . be . true ;
2013-10-11 21:19:01 -04:00
expect ( keychainStub . getReveiverPublicKey . calledTwice ) . to . be . true ;
expect ( pgpStub . decrypt . calledTwice ) . to . be . true ;
2013-09-26 07:26:57 -04:00
expect ( err ) . to . not . exist ;
2013-10-11 21:19:01 -04:00
expect ( emails . length ) . to . equal ( 2 ) ;
2013-09-26 07:26:57 -04:00
done ( ) ;
} ) ;
} ) ;
} ) ;
2013-10-04 11:02:27 -04:00
} ) ;
describe ( 'IMAP: mark message as read' , function ( ) {
it ( 'should work' , function ( done ) {
imapClientStub . updateFlags . yields ( ) ;
2013-09-26 07:26:57 -04:00
2013-10-04 11:02:27 -04:00
emailDao . imapMarkMessageRead ( {
folder : 'asdf' ,
uid : 1
} , function ( err ) {
expect ( imapClientStub . updateFlags . calledOnce ) . to . be . true ;
expect ( err ) . to . not . exist ;
done ( ) ;
} ) ;
} ) ;
2013-08-20 07:30:35 -04:00
} ) ;
2013-10-04 11:02:27 -04:00
2013-08-19 15:13:32 -04:00
} ) ;
} ) ;