203 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			203 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| '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);
 | |
|         }
 | |
|       }
 | |
|     });
 | |
|   });
 | |
| };
 |