mx-connect
Version:
Establish TCP connection to a MX server with MTA-STS and DANE/TLSA support
839 lines (746 loc) • 27.8 kB
JavaScript
/* eslint no-console: 0*/
'use strict';
const mxConnect = require('../lib/mx-connect');
const dane = require('../lib/dane');
const nodeCrypto = require('crypto');
// Helper to create mock socket for testing
function createMockSocket(opts = {}) {
const { EventEmitter } = require('events');
const socket = new EventEmitter();
socket.remoteAddress = opts.remoteAddress || '192.0.2.1';
socket.localAddress = opts.localAddress || '192.0.2.100';
socket.localPort = opts.localPort || 12345;
socket.write = () => true;
socket.end = () => socket.emit('end');
socket.destroy = () => socket.emit('close');
socket.pipe = () => socket;
return socket;
}
/**
* Test DANE module exports
*/
module.exports.daneModuleExports = test => {
test.ok(dane.DANE_USAGE, 'DANE_USAGE should be exported');
test.ok(dane.DANE_SELECTOR, 'DANE_SELECTOR should be exported');
test.ok(dane.DANE_MATCHING_TYPE, 'DANE_MATCHING_TYPE should be exported');
test.ok(dane.EMPTY_DANE_HANDLER, 'EMPTY_DANE_HANDLER should be exported');
test.equal(typeof dane.hasNativeResolveTlsa, 'boolean', 'hasNativeResolveTlsa should be a boolean');
test.equal(typeof dane.resolveTlsaRecords, 'function', 'resolveTlsaRecords should be a function');
test.equal(typeof dane.verifyCertAgainstTlsa, 'function', 'verifyCertAgainstTlsa should be a function');
test.equal(typeof dane.createDaneVerifier, 'function', 'createDaneVerifier should be a function');
test.done();
};
/**
* Test DANE usage constants
*/
module.exports.daneUsageConstants = test => {
test.equal(dane.DANE_USAGE.PKIX_TA, 0, 'PKIX_TA should be 0');
test.equal(dane.DANE_USAGE.PKIX_EE, 1, 'PKIX_EE should be 1');
test.equal(dane.DANE_USAGE.DANE_TA, 2, 'DANE_TA should be 2');
test.equal(dane.DANE_USAGE.DANE_EE, 3, 'DANE_EE should be 3');
test.done();
};
/**
* Test DANE selector constants
*/
module.exports.daneSelectorConstants = test => {
test.equal(dane.DANE_SELECTOR.FULL_CERT, 0, 'FULL_CERT should be 0');
test.equal(dane.DANE_SELECTOR.SPKI, 1, 'SPKI should be 1');
test.done();
};
/**
* Test DANE matching type constants
*/
module.exports.daneMatchingTypeConstants = test => {
test.equal(dane.DANE_MATCHING_TYPE.FULL, 0, 'FULL should be 0');
test.equal(dane.DANE_MATCHING_TYPE.SHA256, 1, 'SHA256 should be 1');
test.equal(dane.DANE_MATCHING_TYPE.SHA512, 2, 'SHA512 should be 2');
test.done();
};
/**
* Test hashCertData function with SHA-256
*/
module.exports.hashCertDataSha256 = test => {
const testData = Buffer.from('test certificate data');
const expectedHash = nodeCrypto.createHash('sha256').update(testData).digest();
const result = dane.hashCertData(testData, dane.DANE_MATCHING_TYPE.SHA256);
test.ok(Buffer.isBuffer(result), 'Result should be a Buffer');
test.ok(expectedHash.equals(result), 'SHA-256 hash should match');
test.done();
};
/**
* Test hashCertData function with SHA-512
*/
module.exports.hashCertDataSha512 = test => {
const testData = Buffer.from('test certificate data');
const expectedHash = nodeCrypto.createHash('sha512').update(testData).digest();
const result = dane.hashCertData(testData, dane.DANE_MATCHING_TYPE.SHA512);
test.ok(Buffer.isBuffer(result), 'Result should be a Buffer');
test.ok(expectedHash.equals(result), 'SHA-512 hash should match');
test.done();
};
/**
* Test hashCertData function with full data (no hash)
*/
module.exports.hashCertDataFull = test => {
const testData = Buffer.from('test certificate data');
const result = dane.hashCertData(testData, dane.DANE_MATCHING_TYPE.FULL);
test.ok(Buffer.isBuffer(result), 'Result should be a Buffer');
test.ok(testData.equals(result), 'Full data should be returned unchanged');
test.done();
};
/**
* Test hashCertData with null input
*/
module.exports.hashCertDataNull = test => {
const result = dane.hashCertData(null, dane.DANE_MATCHING_TYPE.SHA256);
test.equal(result, null, 'Result should be null for null input');
test.done();
};
/**
* Test verifyCertAgainstTlsa with no records
*/
module.exports.verifyCertNoRecords = test => {
const result = dane.verifyCertAgainstTlsa({}, []);
test.equal(result.valid, true, 'Should be valid when no records exist');
test.equal(result.noRecords, true, 'Should indicate no records');
test.equal(result.matchedRecord, null, 'Should have no matched record');
test.done();
};
/**
* Test verifyCertAgainstTlsa with no certificate
*/
module.exports.verifyCertNoCert = test => {
const tlsaRecords = [{ usage: 3, selector: 1, mtype: 1, cert: Buffer.alloc(32) }];
const result = dane.verifyCertAgainstTlsa(null, tlsaRecords);
test.equal(result.valid, false, 'Should be invalid when no certificate');
test.ok(result.error, 'Should have an error message');
test.done();
};
/**
* Test createDaneVerifier returns a function
*/
module.exports.createDaneVerifierReturnsFunction = test => {
const verifier = dane.createDaneVerifier([], {});
test.equal(typeof verifier, 'function', 'Should return a function');
test.done();
};
/**
* Test createDaneVerifier with no records returns undefined (success)
*/
module.exports.createDaneVerifierNoRecords = test => {
const verifier = dane.createDaneVerifier([], {});
const result = verifier('example.com', {});
test.equal(result, undefined, 'Should return undefined (success) when no records');
test.done();
};
/**
* Test EMPTY_DANE_HANDLER
*/
module.exports.emptyDaneHandler = async test => {
test.equal(dane.EMPTY_DANE_HANDLER.enabled, false, 'Should be disabled by default');
const records = await dane.EMPTY_DANE_HANDLER.resolveTlsa('test.example.com');
test.deepEqual(records, [], 'Should return empty array');
test.done();
};
/**
* Test mx-connect exports DANE module
*/
module.exports.mxConnectExportsDane = test => {
test.ok(mxConnect.dane, 'mx-connect should export dane module');
test.equal(typeof mxConnect.dane.resolveTlsaRecords, 'function', 'Should export resolveTlsaRecords');
test.equal(typeof mxConnect.dane.verifyCertAgainstTlsa, 'function', 'Should export verifyCertAgainstTlsa');
test.done();
};
/**
* Test DANE with custom resolver using mock socket
*/
module.exports.daneWithCustomResolver = test => {
let tlsaLookupCalled = false;
const mockResolveTlsa = async () => {
tlsaLookupCalled = true;
// Return empty array to simulate no DANE records
return [];
};
mxConnect(
{
target: 'test.example.com',
mx: [
{
exchange: 'mail.example.com',
priority: 10,
A: ['192.0.2.1'],
AAAA: []
}
],
dane: {
enabled: true,
resolveTlsa: mockResolveTlsa,
logger: () => {}
},
connectHook(delivery, options, callback) {
options.socket = createMockSocket({ remoteAddress: options.host });
return callback();
}
},
(err, connection) => {
test.ifError(err);
test.ok(connection, 'Connection should exist');
test.ok(connection.socket, 'Connection should have socket');
test.ok(tlsaLookupCalled, 'Custom resolveTlsa should have been called');
test.done();
}
);
};
/**
* Test DANE with custom resolver returning TLSA records
*/
module.exports.daneWithTlsaRecords = test => {
let logMessages = [];
// Mock TLSA records (these won't match the actual certificate, but tests the flow)
const mockTlsaRecords = [
{
usage: 3, // DANE-EE
selector: 1, // SPKI
mtype: 1, // SHA-256
cert: Buffer.alloc(32, 0xff), // Fake hash
ttl: 3600
}
];
const mockResolveTlsa = async () => mockTlsaRecords;
mxConnect(
{
target: 'test.example.com',
mx: [
{
exchange: 'mail.example.com',
priority: 10,
A: ['192.0.2.1'],
AAAA: []
}
],
dane: {
enabled: true,
resolveTlsa: mockResolveTlsa,
verify: false, // Don't enforce verification (cert won't match mock)
logger: logObj => {
logMessages.push(logObj);
}
},
connectHook(delivery, options, callback) {
options.socket = createMockSocket({ remoteAddress: options.host });
return callback();
}
},
(err, connection) => {
test.ifError(err);
test.ok(connection, 'Connection should exist');
test.ok(connection.socket, 'Connection should have socket');
// Check that TLSA records were found
const tlsaFoundLog = logMessages.find(log => log.msg === 'TLSA records found');
test.ok(tlsaFoundLog, 'Should log TLSA records found');
test.equal(tlsaFoundLog.recordCount, 1, 'Should have 1 TLSA record');
// Check that DANE was enabled for connection
const daneEnabledLog = logMessages.find(log => log.msg === 'DANE enabled for connection');
test.ok(daneEnabledLog, 'Should log DANE enabled for connection');
// Check connection has DANE properties
test.ok(connection.daneEnabled, 'Connection should have daneEnabled flag');
test.ok(connection.tlsaRecords, 'Connection should have tlsaRecords');
test.equal(connection.tlsaRecords.length, 1, 'Should have 1 TLSA record');
test.done();
}
);
};
/**
* Test DANE with resolver that throws error (verify mode rejects connection)
*/
module.exports.daneResolverError = test => {
const mockResolveTlsa = async () => {
const err = new Error('DNS lookup failed');
err.code = 'ESERVFAIL';
throw err;
};
mxConnect(
{
target: 'test.example.com',
mx: [
{
exchange: 'mail.example.com',
priority: 10,
A: ['192.0.2.1'],
AAAA: []
}
],
dane: {
enabled: true,
resolveTlsa: mockResolveTlsa,
logger: () => {}
},
connectHook(delivery, options, callback) {
options.socket = createMockSocket({ remoteAddress: options.host });
return callback();
}
},
(err, connection) => {
test.ok(err, 'Should return an error when DANE lookup fails in verify mode');
test.ok(!connection, 'Connection should not exist');
test.ok(err.message.includes('DANE TLSA lookup failed'), 'Error should mention DANE lookup failure');
test.equal(err.category, 'dane', 'Error category should be dane');
test.done();
}
);
};
/**
* Test DANE with resolver error and verify:false allows connection
*/
module.exports.daneResolverErrorVerifyFalse = test => {
let logMessages = [];
const mockResolveTlsa = async () => {
const err = new Error('DNS lookup failed');
err.code = 'ESERVFAIL';
throw err;
};
mxConnect(
{
target: 'test.example.com',
mx: [
{
exchange: 'mail.example.com',
priority: 10,
A: ['192.0.2.1'],
AAAA: []
}
],
dane: {
enabled: true,
verify: false,
resolveTlsa: mockResolveTlsa,
logger: logObj => {
logMessages.push(logObj);
}
},
connectHook(delivery, options, callback) {
options.socket = createMockSocket({ remoteAddress: options.host });
return callback();
}
},
(err, connection) => {
test.ifError(err);
test.ok(connection, 'Connection should exist when verify is false');
test.ok(connection.socket, 'Connection should have socket');
// Check that TLSA lookup failure was logged
const failLog = logMessages.find(log => log.msg === 'TLSA lookup failed');
test.ok(failLog, 'Should log TLSA lookup failure');
test.ok(failLog.error, 'Should include error message');
test.done();
}
);
};
/**
* Test DANE with NODATA response (no records exist)
*/
module.exports.daneNoDataResponse = test => {
const mockResolveTlsa = async () => {
const err = new Error('No data');
err.code = 'ENODATA';
throw err;
};
mxConnect(
{
target: 'test.example.com',
mx: [
{
exchange: 'mail.example.com',
priority: 10,
A: ['192.0.2.1'],
AAAA: []
}
],
dane: {
enabled: true,
resolveTlsa: mockResolveTlsa,
logger: () => {}
},
connectHook(delivery, options, callback) {
options.socket = createMockSocket({ remoteAddress: options.host });
return callback();
}
},
(err, connection) => {
test.ifError(err);
test.ok(connection, 'Connection should exist');
test.ok(connection.socket, 'Connection should have socket');
// Should succeed - NODATA means no DANE records, not an error
test.done();
}
);
};
/**
* Test DANE explicitly disabled
*/
module.exports.daneExplicitlyDisabled = test => {
let tlsaLookupCalled = false;
const mockResolveTlsa = async () => {
tlsaLookupCalled = true;
return [];
};
mxConnect(
{
target: 'test.example.com',
mx: [
{
exchange: 'mail.example.com',
priority: 10,
A: ['192.0.2.1'],
AAAA: []
}
],
dane: {
enabled: false,
resolveTlsa: mockResolveTlsa
},
connectHook(delivery, options, callback) {
options.socket = createMockSocket({ remoteAddress: options.host });
return callback();
}
},
(err, connection) => {
test.ifError(err);
test.ok(connection, 'Connection should exist');
test.ok(connection.socket, 'Connection should have socket');
test.ok(!tlsaLookupCalled, 'resolveTlsa should not be called when DANE is disabled');
test.done();
}
);
};
/**
* Test resolveTlsaRecords with custom resolver
*/
module.exports.resolveTlsaRecordsCustomResolver = async test => {
const mockRecords = [{ usage: 3, selector: 1, mtype: 1, cert: Buffer.alloc(32) }];
const mockResolver = async tlsaName => {
test.equal(tlsaName, '_25._tcp.mail.example.com', 'Should format TLSA name correctly');
return mockRecords;
};
const records = await dane.resolveTlsaRecords('mail.example.com', 25, { resolveTlsa: mockResolver });
test.deepEqual(records, mockRecords, 'Should return records from custom resolver');
test.done();
};
/**
* Test resolveTlsaRecords handles ENODATA gracefully
*/
module.exports.resolveTlsaRecordsNoData = async test => {
const mockResolver = async () => {
const err = new Error('No data');
err.code = 'ENODATA';
throw err;
};
const records = await dane.resolveTlsaRecords('mail.example.com', 25, { resolveTlsa: mockResolver });
test.deepEqual(records, [], 'Should return empty array for ENODATA');
test.done();
};
/**
* Test resolveTlsaRecords handles ENOTFOUND gracefully
*/
module.exports.resolveTlsaRecordsNotFound = async test => {
const mockResolver = async () => {
const err = new Error('Not found');
err.code = 'ENOTFOUND';
throw err;
};
const records = await dane.resolveTlsaRecords('mail.example.com', 25, { resolveTlsa: mockResolver });
test.deepEqual(records, [], 'Should return empty array for ENOTFOUND');
test.done();
};
/**
* Test resolveTlsaRecords propagates other errors
*/
module.exports.resolveTlsaRecordsOtherError = async test => {
const mockResolver = async () => {
const err = new Error('Server failure');
err.code = 'ESERVFAIL';
throw err;
};
try {
await dane.resolveTlsaRecords('mail.example.com', 25, { resolveTlsa: mockResolver });
test.ok(false, 'Should have thrown an error');
} catch (err) {
test.equal(err.code, 'ESERVFAIL', 'Should propagate non-NODATA errors');
}
test.done();
};
/**
* Test hasNativeResolveTlsa detection
*/
module.exports.hasNativeResolveTlsaDetection = test => {
const dns = require('dns');
const expected = typeof dns.resolveTlsa === 'function';
test.equal(dane.hasNativeResolveTlsa, expected, 'hasNativeResolveTlsa should match actual dns module');
test.done();
};
/**
* Test DANE with pre-resolved MX that includes TLSA records
*/
module.exports.daneWithPreresolvedMx = test => {
let logMessages = [];
const mockTlsaRecords = [
{
usage: 3,
selector: 1,
mtype: 1,
cert: Buffer.alloc(32, 0xaa)
}
];
mxConnect(
{
target: 'test.example.com',
mx: [
{
exchange: 'mail.example.com',
priority: 10,
A: ['192.0.2.1'],
AAAA: [],
tlsaRecords: mockTlsaRecords
}
],
dane: {
enabled: true,
verify: false,
logger: logObj => {
logMessages.push(logObj);
}
},
connectHook(delivery, options, callback) {
options.socket = createMockSocket({ remoteAddress: options.host });
return callback();
}
},
(err, connection) => {
test.ifError(err);
test.ok(connection, 'Connection should exist');
test.ok(connection.socket, 'Connection should have socket');
// TLSA records should be passed through from pre-resolved MX
test.ok(connection.tlsaRecords, 'Connection should have tlsaRecords');
test.equal(connection.tlsaRecords.length, 1, 'Should have 1 TLSA record');
test.done();
}
);
};
/**
* Test DANE stays disabled without explicit enabled:true
*/
module.exports.daneAutoDetectNoResolver = test => {
let tlsaLookupCalled = false;
mxConnect(
{
target: 'test.example.com',
mx: [
{
exchange: 'mail.example.com',
priority: 10,
A: ['192.0.2.1'],
AAAA: []
}
],
dane: {
// enabled not set - should default to false
resolveTlsa: async () => {
tlsaLookupCalled = true;
return [];
},
logger: () => {}
},
connectHook(delivery, options, callback) {
options.socket = createMockSocket({ remoteAddress: options.host });
return callback();
}
},
(err, connection) => {
test.ifError(err);
test.ok(connection, 'Connection should exist');
test.ok(connection.socket, 'Connection should have socket');
test.ok(!tlsaLookupCalled, 'resolveTlsa should not be called when enabled is not set');
test.done();
}
);
};
/**
* Test extractSPKI with malformed certificate (Issue #1)
*/
module.exports.extractSPKIMalformedCert = test => {
// Test with null
let result = dane.extractSPKI(null);
test.equal(result, null, 'Should return null for null certificate');
// Test with empty object
result = dane.extractSPKI({});
test.equal(result, null, 'Should return null for empty certificate');
// Test with invalid publicKey
result = dane.extractSPKI({ publicKey: 'invalid-key-data' });
test.equal(result, null, 'Should return null for invalid publicKey');
// Test with malformed publicKey buffer
result = dane.extractSPKI({ publicKey: Buffer.from('invalid') });
test.equal(result, null, 'Should return null for malformed publicKey buffer');
test.done();
};
/**
* Test getCertData with malformed certificate (Issue #2)
*/
module.exports.getCertDataMalformedCert = test => {
// Test with null
let result = dane.getCertData(null, dane.DANE_SELECTOR.FULL_CERT);
test.equal(result, null, 'Should return null for null certificate');
// Test with empty object (no raw property)
result = dane.getCertData({}, dane.DANE_SELECTOR.FULL_CERT);
test.equal(result, null, 'Should return null for certificate without raw');
// Test with SPKI selector on malformed cert
result = dane.getCertData({ publicKey: 'invalid' }, dane.DANE_SELECTOR.SPKI);
test.equal(result, null, 'Should return null for malformed certificate with SPKI selector');
test.done();
};
/**
* Test verifyCertAgainstTlsa with malformed TLSA records (Issue #4)
*/
module.exports.verifyCertMalformedTlsaRecords = test => {
const mockCert = {
raw: Buffer.from('test-cert-data'),
publicKey: null
};
// Test with record missing cert field
const recordsNoCert = [{ usage: 3, selector: 0, mtype: 1 }];
let result = dane.verifyCertAgainstTlsa(mockCert, recordsNoCert);
test.equal(result.valid, false, 'Should be invalid when record has no cert field');
// Test with invalid usage value (should not crash)
const recordsInvalidUsage = [{ usage: 99, selector: 0, mtype: 1, cert: Buffer.alloc(32) }];
result = dane.verifyCertAgainstTlsa(mockCert, recordsInvalidUsage);
test.equal(result.valid, false, 'Should be invalid for unknown usage type');
// Test with invalid selector value (should not crash)
const recordsInvalidSelector = [{ usage: 3, selector: 99, mtype: 1, cert: Buffer.alloc(32) }];
result = dane.verifyCertAgainstTlsa(mockCert, recordsInvalidSelector);
test.equal(result.valid, false, 'Should be invalid for unknown selector');
test.done();
};
/**
* Test createDaneVerifier catches exceptions (Issue #1, #2, #4)
*/
module.exports.createDaneVerifierCatchesExceptions = test => {
const tlsaRecords = [
{
usage: 3,
selector: 1,
mtype: 1,
cert: Buffer.alloc(32, 0xff)
}
];
const verifier = dane.createDaneVerifier(tlsaRecords, { verify: true });
// Test with malformed certificate - should not throw
let result;
try {
result = verifier('example.com', { publicKey: 'invalid' });
test.ok(true, 'Should not throw for malformed certificate');
} catch (err) {
test.ok(false, 'Should not throw exception: ' + err.message);
}
// Result should be an error (verification failed), not an exception
test.ok(result instanceof Error || result === undefined, 'Should return error or undefined, not throw');
test.done();
};
/**
* Test isNoRecordsError helper function
*/
module.exports.isNoRecordsErrorHelper = test => {
test.ok(dane.isNoRecordsError, 'isNoRecordsError should be exported');
test.equal(dane.isNoRecordsError('ENODATA'), true, 'ENODATA should be a no-records error');
test.equal(dane.isNoRecordsError('ENOTFOUND'), true, 'ENOTFOUND should be a no-records error');
test.equal(dane.isNoRecordsError('ENOENT'), true, 'ENOENT should be a no-records error');
test.equal(dane.isNoRecordsError('ESERVFAIL'), false, 'ESERVFAIL should not be a no-records error');
test.equal(dane.isNoRecordsError('ETIMEDOUT'), false, 'ETIMEDOUT should not be a no-records error');
test.equal(dane.isNoRecordsError(undefined), false, 'undefined should not be a no-records error');
test.done();
};
/**
* Test hasNativePromiseResolveTlsa detection
*/
module.exports.hasNativePromiseResolveTlsaDetection = test => {
const dns = require('dns');
const expected = dns.promises && typeof dns.promises.resolveTlsa === 'function';
test.equal(dane.hasNativePromiseResolveTlsa, expected, 'hasNativePromiseResolveTlsa should match actual dns.promises module');
test.done();
};
/**
* Test verifyCertAgainstTlsa with DANE-TA without chain (Issue #3)
*/
module.exports.verifyCertDaneTaWithoutChain = test => {
const mockCert = {
raw: Buffer.from('test-cert-data'),
publicKey: null
};
// DANE-TA record without chain should fail with informative error
const daneTeRecords = [
{
usage: 2, // DANE-TA
selector: 0,
mtype: 1,
cert: Buffer.alloc(32, 0xaa)
}
];
const result = dane.verifyCertAgainstTlsa(mockCert, daneTeRecords);
test.equal(result.valid, false, 'Should be invalid when DANE-TA has no chain');
test.ok(result.error, 'Should have error message');
test.ok(result.error.includes('chain'), 'Error should mention chain requirement');
test.done();
};
/**
* Test verifyCertAgainstTlsa with PKIX-TA without chain (Issue #3)
*/
module.exports.verifyCertPkixTaWithoutChain = test => {
const mockCert = {
raw: Buffer.from('test-cert-data'),
publicKey: null
};
// PKIX-TA record without chain should fail with informative error
const pkixTaRecords = [
{
usage: 0, // PKIX-TA
selector: 0,
mtype: 1,
cert: Buffer.alloc(32, 0xaa)
}
];
const result = dane.verifyCertAgainstTlsa(mockCert, pkixTaRecords);
test.equal(result.valid, false, 'Should be invalid when PKIX-TA has no chain');
test.ok(result.error, 'Should have error message');
test.ok(result.error.includes('chain'), 'Error should mention chain requirement');
test.done();
};
/**
* Test hashCertData handles exceptions gracefully
*/
module.exports.hashCertDataHandlesExceptions = test => {
// Test with invalid data type that might cause issues
const result = dane.hashCertData(undefined, dane.DANE_MATCHING_TYPE.SHA256);
test.equal(result, null, 'Should return null for undefined data');
test.done();
};
/**
* Test verifyCertAgainstTlsa with string cert data (hex encoded)
*/
module.exports.verifyCertWithStringCertData = test => {
const testData = Buffer.from('test-cert-data');
const hash = nodeCrypto.createHash('sha256').update(testData).digest();
const mockCert = {
raw: testData
};
// Record with hex-encoded cert data
const records = [
{
usage: 3,
selector: 0,
mtype: 1,
cert: hash.toString('hex') // String instead of Buffer
}
];
const result = dane.verifyCertAgainstTlsa(mockCert, records);
test.equal(result.valid, true, 'Should handle hex-encoded cert data');
test.equal(result.usage, 'DANE-EE', 'Should report DANE-EE usage');
test.done();
};