'use strict'; /*global Promise*/ module.exports.create = function () { throw new Error("greenlock-store-test is a test fixture for greenlock-store-* plugins, not a plugin itself"); }; // ignore all of this, it's just to normalize Promise vs node-style callback thunk vs synchronous function promiseCheckAndCatch(obj, name, mergables) { var promisify = require('util').promisify; // don't loose this-ness, just in case that's important var fn = obj[name].bind(obj); var promiser; // function signature must match, or an error will be thrown if (1 === fn.length) { // wrap so that synchronous errors are caught (alsa handles synchronous results) promiser = function (opts) { return Promise.resolve().then(function () { return fn(merge(mergables, opts)); }); }; } else if (2 === fn.length) { // wrap as a promise promiser = promisify(function (opts, cb) { return fn(merge(mergables, opts), cb); }); } else { return Promise.reject(new Error("'store." + name + "' should accept either one argument, the options," + " and return a Promise or accept two arguments, the options and a node-style callback thunk")); } function shouldntBeNull(result) { if ('undefined' === typeof result) { throw new Error("'store.'" + name + "' should never return `undefined`. Please explicitly return null" + " (or fix the place where a value should have been returned but wasn't)."); } return result; } return function (opts) { return promiser(opts).then(shouldntBeNull); }; } function merge(one, two) { var three = {}; Object.keys(one||{}).forEach(function (k) { if ('undefined' !== typeof one[k]) { three[k] = one[k]; } }); Object.keys(two||{}).forEach(function (k) { if ('undefined' !== typeof two[k]) { three[k] = two[k]; } }); return three; } // Here's the meat, where the tests are happening: module.exports.test = function (store) { var subject = '*.example.com'; var email = 'jon.doe@sbemail.com'; // TODO go just barely beyond dumb storage and provide real keys using 'keypairs.js' var accountKey = { privateKeyPem: 'AccountKey', privateKeyJwk: { account: true } }; var domainKey = { privateKeyPem: 'DomainKey', privateKeyJwk: { domain: true } }; var domainCert = { cert: 'DomainCert', chain: 'DomainChain', privkey: 'DomainKey' , subject: '*.example.com', altnames: ['*.example.com', 'example.com'] , issuedAt: new Date().valueOf(), expiresAt: new Date(Date.now() + 3600000).valueOf() }; var checkCerts = promiseCheckAndCatch(store.certificates, 'check', store.options); var checkCertKey = promiseCheckAndCatch(store.certificates, 'checkKeypair', store.options); var checkAccountKey = promiseCheckAndCatch(store.accounts, 'checkKeypair', store.options); var setAccountKey = promiseCheckAndCatch(store.accounts, 'setKeypair', store.options); var setCertKey = promiseCheckAndCatch(store.certificates, 'setKeypair', store.options); var setCert = promiseCheckAndCatch(store.certificates, 'set', store.options); return checkCerts({ certificate: {}, subject: subject }).then(function (results) { if (null !== results) { throw new Error("should get null when checking certificates for " + subject); } }).then(function () { // TODO check accounts.check var opts = { account: {}, email: email, subject: subject, certificate: {} }; return checkAccountKey(opts).then(function (results) { if (null !== results) { throw new Error("should not have an account keypair for " + subject); } }); }).then(function () { var opts = { account: {}, email: email, keypair: accountKey, subject: subject, certificate: {} }; return setAccountKey(opts).then(function (results) { if ('undefined' === typeof results) { throw new Error("should never get undefined as a return value, did you forget to return?"); } }); }).then(function () { var opts = { certificate: {}, subject: subject, keypair: domainKey, email: email, account: {} }; return setCertKey(opts).then(function (results) { if ('undefined' === typeof results) { throw new Error("should never get undefined as a return value, did you forget to return?"); } }); }).then(function () { var opts = { certificate: {}, subject: subject, pems: domainCert, email: email, account: {} }; return setCert(opts).then(function (results) { if ('undefined' === typeof results) { throw new Error("should never get undefined as a return value, did you forget to return?"); } }); }).then(function () { var opts = { certificate: {}, subject: subject, email: email, account: {} }; return checkCerts(opts).then(function (results) { if (!results || !results.chain || !results.cert) { throw new Error("expected to get certificate and chain for " + subject); } if (domainCert.cert.replace(/\r\n/g, '\n').trim() !== results.cert.replace(/\r\n/g, '\n').trim()) { throw new Error("expected to get exactly the same certificate (aside from CRLF and ending newline) as was set"); } if (domainCert.chain.replace(/\r\n/g, '\n').trim() !== results.chain.replace(/\r\n/g, '\n').trim()) { throw new Error("expected to get exactly the same chain (aside from CRLF and ending newline) as was set"); } if (results.privkey) { if (domainCert.chain.replace(/\r\n/g, '\n').trim() !== results.chain.replace(/\r\n/g, '\n').trim()) { throw new Error("privkey is optional, but when it's set it should be the same string PEM as was set"); } } if (results.altnames) { if (domainCert.altnames.join(' ') !== results.altnames.join(' ')) { throw new Error("altnames sholud be the same type and value as was set"); } } if (results.subject) { if (domainCert.subject !== results.subject) { throw new Error("subject sholud be the same type and value as was set"); } } if (results.issuedAt) { if (domainCert.issuedAt !== results.issuedAt) { throw new Error("issuedAt sholud be the same type and value as was set"); } } if (results.expiresAt) { if (domainCert.expiresAt !== results.expiresAt) { throw new Error("expiresAt sholud be the same type and value as was set"); } } }); }).then(function () { var opts = { certificate: {}, subject: subject, email: email, account: {} }; return checkCertKey(opts).then(function (results) { if (!results) { throw new Error("Should have found a keypair for " + opts.subject); } if (!results.privateKeyPem && !results.privateKeyJwk) { throw new Error("Should have received privateKeyPem and/or privateKeyJwk, but got neither: " + JSON.stringify(results)); } if (results.privateKeyPem) { if (domainCert.privkey.replace(/\r\n/g, '\n').trim() !== results.privateKeyPem.replace(/\r\n/g, '\n').trim()) { throw new Error("privateKeyPem should be the same string PEM as was set"); } } if (results.privateKeyJwk) { if (Object.keys(domainKey.privateKeyJwk).sort().join(' ') !== Object.keys(results.privateKeyJwk).sort().join(' ') ) { throw new Error("privateKeyJwk should be the same string Jwk as was set"); } } }); }).then(function () { var opts = { account: {}, email: email, subject: subject, certificate: {} }; return checkAccountKey(opts).then(function (results) { if (!results) { throw new Error("Should have found a keypair for " + opts.email); } if (!results.privateKeyPem && !results.privateKeyJwk) { throw new Error("Should have received privateKeyPem and/or privateKeyJwk, but got neither: " + JSON.stringify(results)); } if (results.privateKeyPem) { if (accountKey.privateKeyPem.replace(/\r\n/g, '\n').trim() !== results.privateKeyPem.replace(/\r\n/g, '\n').trim() ) { throw new Error("privateKeyPem should be the same string PEM as was set"); } } if (results.privateKeyJwk) { var akeys = Object.keys(accountKey.privateKeyJwk).sort().join(' '); var bkeys = Object.keys(results.privateKeyJwk).sort().join(' '); if (akeys !== bkeys) { throw new Error("privateKeyJwk should be the same string Jwk as was set." + "\nExpected: " + akeys + "\nActual: " + bkeys); } } }); }); };