A simple test suite for Greenlock manager plugins.
Go to file
AJ ONeal 19b571f088 update docs 2019-10-31 00:12:01 -06:00
tests v3.0.0: a nice test suite for Greenlock Managers 2019-10-31 00:07:33 -06:00
.gitignore Initial commit 2019-10-31 04:56:52 +00:00
.prettierrc v3.0.0: a nice test suite for Greenlock Managers 2019-10-31 00:07:33 -06:00
LICENSE Initial commit 2019-10-31 04:56:52 +00:00
README.md update docs 2019-10-31 00:12:01 -06:00
package-lock.json v3.0.0: a nice test suite for Greenlock Managers 2019-10-31 00:07:33 -06:00
package.json v3.0.0: a nice test suite for Greenlock Managers 2019-10-31 00:07:33 -06:00
tester.js v3.0.0: a nice test suite for Greenlock Managers 2019-10-31 00:07:33 -06:00

README.md

greenlock-manager-test.js

A simple test suite for Greenlock manager plugins.

Greenlock Manager

A greenlock manager is just a set of a few callbacks to keeps track of:

  • Default settings that apply to all sites such as
    • subscriberEmail
    • agreeToTerms
    • store (the account key and ssl certificate store)
  • Site settings such as
    • subject (ex: example.com)
    • altnames (ex: example.com,www.example.com)
    • renewAt (ex: '45d')
    • challenges (plugins for 'http-01', 'dns-01', etc)

The callbacks are:

  • set({ subject, altnames, renewAt }) to save site details
  • find({ subject, altnames, renewBefore }) which returns a list of matching sites (perhaps all sites)
  • remove({ subject }) which marks a site as deleted
  • defaults() which either gets or sets the global configs that apply to all sites

When do they get called? Well, whenever they need to.

Some Terminology

  • subject refers to the primary domain on an SSL certificate
  • altnames refers to the list of domain names on the certificate (including the subject)
  • renewAt is a pre-calculated value based on expiresAt or issuedAt on the certificate

Those are the only values you really have to worry about.

The rest you can make up for your own needs, or they're just opaque values you'll get from Greenlock.

Do you want to build a plugin?

You can start really simple: just make a file that exports a create() function:

A great first, failing plugin:

my-plugin.js:

'use strict';

var MyManager = module.exports;

MyManager.create = function(options) {
    console.log('The tests will make me stronger');
    return {};
};

The test suite from heaven

You write your test file, run it, and then you get a play-by-play of what to do.

npm install --save-dev greenlock-manager-test

test.js:

'use strict';

var Tester = require('greenlock-manager-test');
var MyManager = require('./');
var myConfigOptions = {
    someApiTokenForMyManager: 'xxx'
};

Tester.test(MyManager, myConfigOptions)
    .then(function() {
        console.log('All Tests Passed');
    })
    .catch(function(err) {
        console.error('Oops... something bad happened:');
        console.error(err);
    });

You just follow the error messages and, which a little help from this README, bam!, you get a working plugin. It's insane!

The lazy, hacky way.

If you're going to publish a module, you should pass the full test suite.

If not, eh, you can be lazy.

Bare minimum...

At a bare minimum, you must implement find() to return an array of { subject, altnames }.

For example:

function find(argsToIgnore) {
    return Promise.resolve([
        { subject: 'example.com', altnames: ['example.com', 'www.example.com'] }
    ]);
}

If that's absolutely all that you do, all of the other methods will be implemented around greenlock-manager-fs.

The Right Way™

If you want to publish a module to the community you should do a slightly better job:

module.exports.create = function(options) {
    var manager = {};

    // add some things to... wherever you save things

    manager.set = async function(siteConfig) {
        // You can see in the tests a sample of common values,
        // but you don't really need to worry about it.
        var subject = siteConfig;

        // Cherry pick what you like for indexing / search, and JSONify the rest
        return mergeOrCreateSite(subject, siteConfig);
    };

    // find the things you've saved before

    manager.find = async function({ subject, altnames, renewBefore }) {
        var results = [];
        var gotten = {};

        if (subject) {
            var site = await getSiteBySubject(subject);
            if (site) {
                results.push(site);
                gotten[site.subject] = true;
            }
        }

        if (altnames) {
            var sites = await getSiteByAltnames(subject);
            sites.forEach(function() {});
            if (site) {
                if (!gotten[site.subject]) {
                    results.push(site);
                    gotten[site.subject] = true;
                }
            }
        }

        if (subject || altnames) {
            return results;
        }

        if (renewBefore) {
            return getSitesThatShouldBeRenewedBefore(renewBefore);
        }

        return getAllSites();
    };

    // delete a site config

    manager.remove = async function({ subject }) {
        // set deletedAt to a value, or actually delete it - however you like
        return mergeOrCreateSite(subject, { deletedAt: Date.now() });
    };

    // get / set global things

    manager.defaults = async function(options) {
        if (!options) {
            return getDefaultConfigValues();
        }

        return mergeDefaultConfigValues(options);
    };

    // optional, if you need it

    manager.init = async function(deps) {
        // a place to do some init, if you need it

        return doMyInit();

        // Also, `deps` will have some common dependencies
        // than many modules need, such as `request`.
        // This cuts down on stray dependencies, and helps
        // with browser compatibility.

        request = deps.request;
    };
};

How to use your plugin

The Right Way:

var Greenlock = require('greenlock');
var greenlock = Greenlock.create({
    manager: '/absolute/path/to/manager'
    someOptionYouWant: true,
});

Why no require?

Okay, so you expect it to look like this:

var Greenlock = require('greenlock');
var greenlock = Greenlock.create({
    // WRONG!!
    manager: require('./relative/path/to/manager').create({
        someOptionYouWant: true
    })
});

NOPE!

It just has to do with some plugin architecture decisions around making the configuration serializable.

I may go back and add the other way, but this is how it is right now.