mardi 31 mars 2015

Testing promises and sync functions that throw errors

I'm trying to build and test a function at the same time. Testing makes sense and I love it in theory, but when It comes down to it it always is a pain in the behind.


I have a function that takes a string and throws errors when something goes awry if all goes well it's going to return the original text argument and therefore a truthy value, if not it should be caught by the promise it's either in or itself as the promise.


This is the test / what I actually want to do (which doesn't work).



var main = require("./index.js")
var Promise = require("bluebird")
var mocha = require("mocha")
var chai = require("chai")
var chaiPromise = require("chai-as-promised")
chai.use(chaiPromise)

var shouldThrow = [
"random", // invalid non-flag
"--random", // invalid flag
"--random string", //invalid flag
"--wallpaper", // invalid flag w/ match
"--notify", // invalid flag w/ match
"wallpaper", // valid non-flag missing option(s) image
"wallpaper image.jpg" // invalid flag value
"wallpaper http://ift.tt/1G3L9LT", // invalid flag value
"wallpaper //cdn.shopify.com/s/files/1/0031/5352/t/28/assets/favicon.ico?12375621748379006621", // invalid flag value
"wallpaper http://ift.tt/1FecoQO", // invalid flag value
"wallpaper http://ift.tt/1G3L9LV", // invalid flag value
"wallpaper http://ift.tt/1Fecqs1", // invalid flag value
"wallpaper http://ift.tt/1G3L76O --queue", // invalid flag value
"wallpaper http://ift.tt/1G3L76O --queue "+moment().subtract(1, "month").format("YYYY-MM-DD-HH-mm"), // invalid flag value
"wallpaper http://ift.tt/1G3L76O --queue "+moment().add(1, "month").format("YY-MM-DD-HH"), // invalid flag value
"wallpaper --image http://ift.tt/1G3L9LT", // invalid flag value not https
"wallpaper --image //cdn.shopify.com/s/files/1/0031/5352/t/28/assets/favicon.ico?12375621748379006621", // invalid flag no protocol
"wallpaper --image http://ift.tt/1FecoQO", // invalid flag value not https
"wallpaper --image http://ift.tt/1G3L9LV", // invalid flag value not valid image
"wallpaper --image http://ift.tt/1Fecqs1", // invalid flag image not found
"wallpaper --image http://ift.tt/1G3L76O --queue", // invalid subflag queue missing value
"wallpaper --image http://ift.tt/1G3L76O --queue "+moment().subtract(1, "month").format("YYYY-MM-DD-HH-mm"), // invalid subflag queue date value is past
"wallpaper --image http://ift.tt/1G3L76O --queue "+moment().add(1, "month").format("YY-MM-DD-HH"), // invalid subflag queue date value format
"--wallpaper --image http://ift.tt/1G3L76O", //no action non-flag
"--wallpaper --image http://ift.tt/1G3L76O --queue "+moment().add(1, "month").format("YYYY-MM-DD-HH-mm"), //no action non-flag
"notify", // valid non-flag missing option(s) message, open
'notify --message "Hello world"', // valid flag missing params open
'notify --open "https://www.holstee.com"', // valid flag missing params message
'notify --message "Hello world" --open "http://www.holstee.com"', // invalid subflag value `open` should be https
'notify --message "Hello world" --open "https://www.holstee.com" --queue', // invalid subflag queue missing value
'notify --message "Hello world" --open "https://www.holstee.com" --queue '+moment().subtract(1, "month").format("YYYY-MM-DD-HH-mm"), // invalid subflag queue date value is past
'notify --message "Hello world" --open "https://www.holstee.com" --queue '+moment().add(1, "month").format("YY-MM-DD-HH"), // invalid subflag queue date value format
'--notify --message "Hello world" --open "https://www.holstee.com"', //no action non-flag
'--notify --message "Hello world" --open "https://www.holstee.com --queue "'+moment().add(1, "month").format("YYYY-MM-DD-HH-mm"), //no action non-flag
]

var shouldNotThrow = [
'notify --message "Hello world" --open "https://www.holstee.com"',
'notify --message "Hello world" --open "https://www.holstee.com --queue "'+moment().add(1, "month").format("YYYY-MM-DD-HH-mm"),
"wallpaper --image http://ift.tt/1G3L76O",
"wallpaper --image http://ift.tt/1G3L76O --queue "+moment().add(1, "month").format("YYYY-MM-DD-HH-mm"),
]

describe('Process Text', function(){
return Promise.each(shouldThrow, function(option){
it('throw error', function(){
return main.processText(option).should.throw()
})
})
return Promise.each(shouldNotThrow, function(option){
it('throw error', function(){
return main.processText(option).should.not.throw()
})
})
})


Here's a snapshot of the non-working* function I'm trying to test.



main.processText = function(text){
// general validation
var args = minimist(text.split(" "))
var argKeys = _.chain(args).keys().without("_").value()
var topLevelFlags = _.keys(flags)
var topLevelActions = _.chain(flags).keys().without("queue").value()
var allFlags = _.chain(flags).map(function(subFlags, key){
return subFlags.concat(key)
}).flatten().value()

var accidental = _.intersection(allFlags, args._)
var correct = _.map(accidental, function(flag){
return "--"+flag
})
if(accidental.length) throw new Error("non-flag data present / "+ accidental.join(",") + " should be: " + correct.join(","))
if(!args._.length) throw new Error("invalid non-flag data present")

var difference = _.difference(allFlags, argKeys)
var intersection = _.intersection(allFlags, argKeys)
var invalid = _.without.apply(_, [argKeys].concat(intersection))
if(intersection.length !== argKeys.length) throw new Error("invalid flags / "+ invalid)
var topLevelIntersection = _.intersection(topLevelActions, argKeys)
if(topLevelIntersection.length > 1) throw new Error("too many top-level flags")
if(args.wallpaper){
// wallpaer validation
var parsedUrl = url.parse(args.wallpaper)
if(!parsedUrl.hostname) throw new Error("hostname is missing, might be local file reference")
if(parsedUrl.protocol !== "https") throw new Error("image protocol should be https")
var fileExtension = path.extname(parsedUrl.path)
if(!_.contains([".png", ".jpg", ".jpeg"], fileExtension)) throw new Error("wallpaper image is invalid file type")
}else if(args.notify){
// notify validation
if(args.notify !== true) throw new Error("notify shouldn't have value")
var notifyIntersection = _.intersection(args.notify, flags.notify)
var missing = _.without.apply(_, [args.notify].concat(notifyIntersection))
if(missing) throw new Error("notify missing required param: "+ missing.join(","))
}
}


Note its not a promise and doesn't return any promises yet. One of the validation features I want is to check a if a url responds in a 200 status code, that's gonna be a request promise. If I update this function then does all of the function contents need to be nested within a Promise.resolve(false).then()? Perhaps the promise shouldn't be in this block of code and all async validation operations should exist somewhere else?


I don't know what I'm doing and I'm a little frustrated. I'm of course looking for some golden bullet or whatever that will make sense of all this.


Ideally I could use some help on how to test this kind of function. If I make it into a promise later on I still want all my tests to work.


Aucun commentaire:

Enregistrer un commentaire