Conflicts:
lib/http_router/version.rb test/common/recognize.txt
This commit is contained in:
parent
1c4f5d88c5
commit
cdc73f391a
33
Rakefile
33
Rakefile
|
@ -1,9 +1,33 @@
|
|||
# encoding: utf-8
|
||||
require 'bundler'
|
||||
Bundler::GemHelper.install_tasks
|
||||
Rake::Task['release'].enhance([:test, :release_js])
|
||||
|
||||
task :release_js do
|
||||
$: << 'lib'
|
||||
require 'http_router/version'
|
||||
File.open('js/package.json', 'w') do |f|
|
||||
f << <<-EOT
|
||||
{
|
||||
"name": "http_router",
|
||||
"description": "URL routing and generation in js",
|
||||
"author": "Joshua Hull <joshbuddy@gmail.com>",
|
||||
"version": "#{HttpRouter::VERSION}",
|
||||
"directories": {
|
||||
"lib" : "./lib/http_router"
|
||||
},
|
||||
"main": "lib/http_router"
|
||||
}
|
||||
EOT
|
||||
end
|
||||
sh "cd js && npm publish"
|
||||
sh "git commit js/package.json -m'bumped js version'"
|
||||
end
|
||||
|
||||
test_tasks = ['test:generation', 'test:recognition', 'test:integration', 'test:examples', 'test:rdoc_examples']
|
||||
test_tasks << 'test:js' if `which coffee && which node` && $?.success?
|
||||
desc "Run all tests"
|
||||
task :test => ['test:generation', 'test:recognition', 'test:integration', 'test:examples', 'test:rdoc_examples']
|
||||
task :test => test_tasks
|
||||
|
||||
require 'pp'
|
||||
|
||||
|
@ -22,6 +46,13 @@ namespace :test do
|
|||
Dir['./test/**/test_*.rb'].each { |test| require test }
|
||||
end
|
||||
|
||||
desc "Run js tests"
|
||||
task :js do
|
||||
sh "coffee -c js/test/test.coffee"
|
||||
sh "coffee -c js/lib/http_router.coffee"
|
||||
sh "node js/test/test.js"
|
||||
end
|
||||
|
||||
desc "Run generic recognition tests"
|
||||
task :recognition do
|
||||
$: << 'lib'
|
||||
|
|
|
@ -0,0 +1,368 @@
|
|||
root.Sherpa = class Sherpa
|
||||
constructor: (@callback) ->
|
||||
@root = new Node()
|
||||
@routes = {}
|
||||
match: (httpRequest, httpResponse) ->
|
||||
request = if (httpRequest.url?) then new Request(httpRequest) else new PathRequest(httpRequest)
|
||||
@root.match(request)
|
||||
if request.destinations.length > 0
|
||||
new Response(request, httpResponse).invoke()
|
||||
else if @callback?
|
||||
@callback(request.underlyingRequest)
|
||||
findSubparts: (part) ->
|
||||
subparts = []
|
||||
while match = part.match(/\\.|[:*][a-z0-9_]+|[^:*\\]+/)
|
||||
part = part.slice(match.index, part.length)
|
||||
subparts.push part.slice(0, match[0].length)
|
||||
part = part.slice(match[0].length, part.length)
|
||||
subparts
|
||||
generatePaths: (path) ->
|
||||
[paths, chars, startIndex, endIndex] = [[''], path.split(''), 0, 1]
|
||||
for charIndex in [0...chars.length]
|
||||
c = chars[charIndex]
|
||||
switch c
|
||||
when '\\'
|
||||
# do nothing ...
|
||||
charIndex++
|
||||
add = if chars[charIndex] == ')' or chars[charIndex] == '('
|
||||
chars[charIndex]
|
||||
else
|
||||
"\\#{chars[charIndex]}"
|
||||
paths[pathIndex] += add for pathIndex in [startIndex...endIndex]
|
||||
when '('
|
||||
# over current working set, double paths
|
||||
paths.push(paths[pathIndex]) for pathIndex in [startIndex...endIndex]
|
||||
# move working set to newly copied paths
|
||||
startIndex = endIndex
|
||||
endIndex = paths.length
|
||||
when ')'
|
||||
startIndex -= endIndex - startIndex
|
||||
else
|
||||
paths[pathIndex] += c for pathIndex in [startIndex...endIndex]
|
||||
paths.reverse()
|
||||
paths
|
||||
url: (name, params) ->
|
||||
@routes[name]?.url(params)
|
||||
addComplexPart: (subparts, compiledPath, matchesWith, variableNames) ->
|
||||
escapeRegexp = (str) -> str.replace(/([\.*+?^=!:${}()|[\]\/\\])/g, '\\$1')
|
||||
[capturingIndicies, splittingIndicies, captures, spans] = [[], [], 0, false]
|
||||
regexSubparts = for part in subparts
|
||||
switch part[0]
|
||||
when '\\'
|
||||
compiledPath.push "'#{part[1]}'"
|
||||
escapeRegexp(part[1])
|
||||
when ':', '*'
|
||||
spans = true if part[0] == '*'
|
||||
captures += 1
|
||||
name = part.slice(1, part.length)
|
||||
variableNames.push(name)
|
||||
if part[0] == '*'
|
||||
splittingIndicies.push(captures)
|
||||
compiledPath.push "params['#{name}'].join('/')"
|
||||
else
|
||||
capturingIndicies.push(captures)
|
||||
compiledPath.push "params['#{name}']"
|
||||
if spans
|
||||
if matchesWith[name]? then "((?:#{matchesWith[name].source}\\/?)+)" else '(.*?)'
|
||||
else
|
||||
"(#{(matchesWith[name]?.source || '[^/]*?')})"
|
||||
else
|
||||
compiledPath.push "'#{part}'"
|
||||
escapeRegexp(part)
|
||||
regexp = new RegExp("#{regexSubparts.join('')}$")
|
||||
if spans
|
||||
new SpanningRegexMatcher(regexp, capturingIndicies, splittingIndicies)
|
||||
else
|
||||
new RegexMatcher(regexp, capturingIndicies, splittingIndicies)
|
||||
addSimplePart: (subparts, compiledPath, matchesWith, variableNames) ->
|
||||
part = subparts[0]
|
||||
switch part[0]
|
||||
when ':'
|
||||
variableName = part.slice(1, part.length)
|
||||
compiledPath.push "params['#{variableName}']"
|
||||
variableNames.push(variableName)
|
||||
if matchesWith[variableName]? then new SpanningRegexMatcher(matchesWith[variableName], [0], []) else new Variable()
|
||||
when '*'
|
||||
compiledPath.push "params['#{variableName}'].join('/')"
|
||||
variableName = part.slice(1, part.length)
|
||||
variableNames.push(variableName)
|
||||
new Glob(matchesWith[variableName])
|
||||
else
|
||||
compiledPath.push "'#{part}'"
|
||||
new Lookup(part)
|
||||
add: (rawPath, opts) ->
|
||||
matchesWith = opts?.matchesWith || {}
|
||||
defaults = opts?.default || {}
|
||||
routeName = opts?.name
|
||||
partiallyMatch = false
|
||||
route = if rawPath.exec?
|
||||
new Route([@root.add(new RegexPath(@root, rawPath))])
|
||||
else
|
||||
if rawPath.substring(rawPath.length - 1) == '*'
|
||||
rawPath = rawPath.substring(0, rawPath.length - 1)
|
||||
partiallyMatch = true
|
||||
pathSet = for path in @generatePaths(rawPath)
|
||||
node = @root
|
||||
variableNames = []
|
||||
parts = path.split('/')
|
||||
compiledPath = []
|
||||
for part in parts
|
||||
unless part == ''
|
||||
compiledPath.push "'/'"
|
||||
subparts = @findSubparts(part)
|
||||
nextNodeFn = if subparts.length == 1 then @addSimplePart else @addComplexPart
|
||||
node = node.add(nextNodeFn(subparts, compiledPath, matchesWith, variableNames))
|
||||
if opts?.conditions?
|
||||
node = node.add(new RequestMatcher(opts.conditions))
|
||||
path = new Path(node, variableNames)
|
||||
path.partial = partiallyMatch
|
||||
path.compiled = if compiledPath.length == 0 then "'/'" else compiledPath.join('+')
|
||||
path
|
||||
new Route(pathSet, matchesWith)
|
||||
route.default = defaults
|
||||
route.name = routeName
|
||||
@routes[routeName] = route if routeName?
|
||||
route
|
||||
|
||||
class Response
|
||||
constructor: (@request, @httpResponse, @position) ->
|
||||
@position ||= 0
|
||||
next: ->
|
||||
if @position == @destinations.length - 1
|
||||
false
|
||||
else
|
||||
new Response(@request, @httpResponse, @position + 1).invoke()
|
||||
invoke: ->
|
||||
req = if typeof(@request.underlyingRequest) == 'string' then {} else @request.underlyingRequest
|
||||
req.params = @request.destinations[@position].params
|
||||
req.route = @request.destinations[@position].route
|
||||
req.pathInfo = @request.destinations[@position].pathInfo
|
||||
@request.destinations[@position].route.destination(req, @httpResponse)
|
||||
|
||||
class Node
|
||||
constructor: ->
|
||||
@type ||= 'node'
|
||||
@matchers = []
|
||||
add: (n) ->
|
||||
@matchers.push(n) if !@matchers[@matchers.length - 1]?.usable(n)
|
||||
@matchers[@matchers.length - 1].use(n)
|
||||
usable: (n) -> n.type == @type
|
||||
match: (request) ->
|
||||
m.match(request) for m in @matchers
|
||||
superMatch: Node::match
|
||||
use: (n) -> this
|
||||
|
||||
class Lookup extends Node
|
||||
constructor: (part) ->
|
||||
@part = part
|
||||
@type = 'lookup'
|
||||
@map = {}
|
||||
super
|
||||
match: (request) ->
|
||||
if @map[request.path[0]]?
|
||||
request = request.clone()
|
||||
part = request.path.shift()
|
||||
@map[part].match(request)
|
||||
use: (n) ->
|
||||
@map[n.part] ||= new Node()
|
||||
@map[n.part]
|
||||
|
||||
class Variable extends Node
|
||||
constructor: ->
|
||||
@type ||= 'variable'
|
||||
super
|
||||
match: (request) ->
|
||||
if request.path.length > 0
|
||||
request = request.clone()
|
||||
request.variables.push(request.path.shift())
|
||||
super(request)
|
||||
|
||||
class Glob extends Variable
|
||||
constructor: (@regexp) ->
|
||||
@type = 'glob'
|
||||
super
|
||||
match: (request) ->
|
||||
if request.path.length > 0
|
||||
original_request = request
|
||||
cloned_path = request.path.slice(0, request.path)
|
||||
for i in [1..original_request.path.length]
|
||||
request = original_request.clone()
|
||||
match = request.path[i - 1].match(@regexp) if @regexp?
|
||||
return if @regexp? and (!match? or match[0].length != request.path[i - 1].length)
|
||||
request.variables.push(request.path.slice(0, i))
|
||||
request.path = request.path.slice(i, request.path.length)
|
||||
@superMatch(request)
|
||||
|
||||
class RegexMatcher extends Node
|
||||
constructor: (@regexp, @capturingIndicies, @splittingIndicies) ->
|
||||
@type ||= 'regex'
|
||||
@varIndicies = []
|
||||
@varIndicies[i] = [i, 'split'] for i in @splittingIndicies
|
||||
@varIndicies[i] = [i, 'capture'] for i in @capturingIndicies
|
||||
@varIndicies.sort (a, b) -> a[0] - b[0]
|
||||
super
|
||||
match: (request) ->
|
||||
if request.path[0]? and match = request.path[0].match(@regexp)
|
||||
return unless match[0].length == request.path[0].length
|
||||
request = request.clone()
|
||||
@addVariables(request, match)
|
||||
request.path.shift()
|
||||
super(request)
|
||||
addVariables: (request, match) ->
|
||||
for v in @varIndicies when v?
|
||||
idx = v[0]
|
||||
type = v[1]
|
||||
switch type
|
||||
when 'split' then request.variables.push match[idx].split('/')
|
||||
when 'capture' then request.variables.push match[idx]
|
||||
usable: (n) ->
|
||||
n.type == @type && n.regexp == @regexp && n.capturingIndicies == @capturingIndicies && n.splittingIndicies == @splittingIndicies
|
||||
|
||||
class SpanningRegexMatcher extends RegexMatcher
|
||||
constructor: (@regexp, @capturingIndicies, @splittingIndicies) ->
|
||||
@type = 'spanning'
|
||||
super
|
||||
match: (request) ->
|
||||
if request.path.length > 0
|
||||
wholePath = request.wholePath()
|
||||
if match = wholePath.match(@regexp)
|
||||
return unless match.index == 0
|
||||
request = request.clone()
|
||||
@addVariables(request, match)
|
||||
request.path = request.splitPath(wholePath.slice(match.index + match[0].length, wholePath.length))
|
||||
@superMatch(request)
|
||||
|
||||
class RequestMatcher extends Node
|
||||
constructor: (@conditions) ->
|
||||
@type = 'request'
|
||||
super
|
||||
match: (request) ->
|
||||
conditionCount = 0
|
||||
satisfiedConditionCount = 0
|
||||
for type, matcher of @conditions
|
||||
val = request.underlyingRequest[type]
|
||||
conditionCount++
|
||||
v = if matcher instanceof Array
|
||||
matching = ->
|
||||
for cond in matcher
|
||||
if cond.exec?
|
||||
return true if matcher.exec(val)
|
||||
else
|
||||
return true if cond == val
|
||||
false
|
||||
matching()
|
||||
else
|
||||
if matcher.exec? then matcher.exec(val) else matcher == val
|
||||
satisfiedConditionCount++ if v
|
||||
if conditionCount == satisfiedConditionCount
|
||||
super(request)
|
||||
usable: (n) ->
|
||||
n.type == @type && n.conditions == @conditions
|
||||
|
||||
class Path extends Node
|
||||
constructor: (@parent, @variableNames) ->
|
||||
@type = 'path'
|
||||
@partial = false
|
||||
addDestination: (request) -> request.destinations.push({route: @route, request: request, params: @constructParams(request)})
|
||||
match: (request) ->
|
||||
if @partial or request.path.length == 0
|
||||
@addDestination(request)
|
||||
if @partial
|
||||
request.destinations[request.destinations.length - 1].pathInfo = "/#{request.wholePath()}"
|
||||
constructParams: (request) ->
|
||||
params = {}
|
||||
for i in [0...@variableNames.length]
|
||||
params[@variableNames[i]] = request.variables[i]
|
||||
params
|
||||
url: (rawParams) ->
|
||||
rawParams = {} unless rawParams?
|
||||
params = {}
|
||||
for key in @variableNames
|
||||
params[key] = if @route.default? then rawParams[key] || @route.default[key] else rawParams[key]
|
||||
return undefined if !params[key]?
|
||||
for name in @variableNames
|
||||
if @route.matchesWith[name]?
|
||||
match = params[name].match(@route.matchesWith[name])
|
||||
return undefined unless match? && match[0].length == params[name].length
|
||||
path = if @compiled == '' then '' else eval(@compiled)
|
||||
if path?
|
||||
delete rawParams[name] for name in @variableNames
|
||||
path
|
||||
|
||||
class RegexPath extends Path
|
||||
constructor: (@parent, @regexp) ->
|
||||
@type = 'regexp_route'
|
||||
super
|
||||
match: (request) ->
|
||||
request.regexpRouteMatch = @regexp.exec(request.decodedPath())
|
||||
if request.regexpRouteMatch? && request.regexpRouteMatch[0].length == request.decodedPath().length
|
||||
request = request.clone()
|
||||
request.path = []
|
||||
super(request)
|
||||
constructParams: (request) -> request.regexpRouteMatch
|
||||
url: (rawParams) -> throw("This route cannot be generated")
|
||||
|
||||
class Route
|
||||
constructor: (@pathSet, @matchesWith) ->
|
||||
path.route = this for path in @pathSet
|
||||
to: (@destination) ->
|
||||
path.parent.add(path) for path in @pathSet
|
||||
generateQuery: (params, base, query) ->
|
||||
query = ""
|
||||
base ||= ""
|
||||
if params?
|
||||
if params instanceof Array
|
||||
for idx in [0...(params.length)]
|
||||
query += @generateQuery(params[idx], "#{base}[]")
|
||||
else if params instanceof Object
|
||||
for k,v of params
|
||||
query += @generateQuery(v, if base == '' then k else "#{base}[#{k}]")
|
||||
else
|
||||
query += encodeURIComponent(base).replace(/%20/g, '+')
|
||||
query += '='
|
||||
query += encodeURIComponent(params).replace(/%20/g, '+')
|
||||
query += '&'
|
||||
query
|
||||
url: (params) ->
|
||||
path = undefined
|
||||
for pathObj in @pathSet
|
||||
path = pathObj.url(params)
|
||||
break if path?
|
||||
if path?
|
||||
query = @generateQuery(params)
|
||||
joiner = if query != '' then '?' else ''
|
||||
"#{encodeURI(path)}#{joiner}#{query.substr(0, query.length - 1)}"
|
||||
else
|
||||
undefined
|
||||
|
||||
class Request
|
||||
constructor: (@underlyingRequest, @callback) ->
|
||||
@variables = []
|
||||
@destinations = []
|
||||
if @underlyingRequest?
|
||||
@path = @splitPath()
|
||||
toString: -> "<Request path: /#{@path.join('/') } #{@path.length}>"
|
||||
wholePath: -> @path.join('/')
|
||||
decodedPath: (path) ->
|
||||
unless path?
|
||||
path = require('url').parse(@underlyingRequest.url).pathname
|
||||
decodeURI(path)
|
||||
splitPath: (path) ->
|
||||
decodedPath = @decodedPath(path)
|
||||
splitPath = if decodedPath == '/' then [] else decodedPath.split('/')
|
||||
splitPath.shift() if splitPath[0] == ''
|
||||
splitPath
|
||||
clone: ->
|
||||
c = new Request()
|
||||
c.path = @path.slice(0, @path.length)
|
||||
c.variables = @variables.slice(0, @variables.length)
|
||||
c.underlyingRequest = @underlyingRequest
|
||||
c.callback = @callback
|
||||
c.destinations = @destinations
|
||||
c
|
||||
|
||||
class PathRequest extends Request
|
||||
decodedPath: (path) ->
|
||||
unless path?
|
||||
path = @underlyingRequest
|
||||
decodeURI(path)
|
|
@ -0,0 +1,668 @@
|
|||
(function() {
|
||||
var Sherpa;
|
||||
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
|
||||
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
|
||||
function ctor() { this.constructor = child; }
|
||||
ctor.prototype = parent.prototype;
|
||||
child.prototype = new ctor;
|
||||
child.__super__ = parent.prototype;
|
||||
return child;
|
||||
};
|
||||
root.Sherpa = Sherpa = (function() {
|
||||
var Glob, Lookup, Node, Path, PathRequest, RegexMatcher, RegexPath, Request, RequestMatcher, Response, Route, SpanningRegexMatcher, Variable;
|
||||
function Sherpa(callback) {
|
||||
this.callback = callback;
|
||||
this.root = new Node();
|
||||
this.routes = {};
|
||||
}
|
||||
Sherpa.prototype.match = function(httpRequest, httpResponse) {
|
||||
var request;
|
||||
request = (httpRequest.url != null) ? new Request(httpRequest) : new PathRequest(httpRequest);
|
||||
this.root.match(request);
|
||||
if (request.destinations.length > 0) {
|
||||
return new Response(request, httpResponse).invoke();
|
||||
} else if (this.callback != null) {
|
||||
return this.callback(request.underlyingRequest);
|
||||
}
|
||||
};
|
||||
Sherpa.prototype.findSubparts = function(part) {
|
||||
var match, subparts;
|
||||
subparts = [];
|
||||
while (match = part.match(/\\.|[:*][a-z0-9_]+|[^:*\\]+/)) {
|
||||
part = part.slice(match.index, part.length);
|
||||
subparts.push(part.slice(0, match[0].length));
|
||||
part = part.slice(match[0].length, part.length);
|
||||
}
|
||||
return subparts;
|
||||
};
|
||||
Sherpa.prototype.generatePaths = function(path) {
|
||||
var add, c, charIndex, chars, endIndex, pathIndex, paths, startIndex, _ref, _ref2;
|
||||
_ref = [[''], path.split(''), 0, 1], paths = _ref[0], chars = _ref[1], startIndex = _ref[2], endIndex = _ref[3];
|
||||
for (charIndex = 0, _ref2 = chars.length; 0 <= _ref2 ? charIndex < _ref2 : charIndex > _ref2; 0 <= _ref2 ? charIndex++ : charIndex--) {
|
||||
c = chars[charIndex];
|
||||
switch (c) {
|
||||
case '\\':
|
||||
charIndex++;
|
||||
add = chars[charIndex] === ')' || chars[charIndex] === '(' ? chars[charIndex] : "\\" + chars[charIndex];
|
||||
for (pathIndex = startIndex; startIndex <= endIndex ? pathIndex < endIndex : pathIndex > endIndex; startIndex <= endIndex ? pathIndex++ : pathIndex--) {
|
||||
paths[pathIndex] += add;
|
||||
}
|
||||
break;
|
||||
case '(':
|
||||
for (pathIndex = startIndex; startIndex <= endIndex ? pathIndex < endIndex : pathIndex > endIndex; startIndex <= endIndex ? pathIndex++ : pathIndex--) {
|
||||
paths.push(paths[pathIndex]);
|
||||
}
|
||||
startIndex = endIndex;
|
||||
endIndex = paths.length;
|
||||
break;
|
||||
case ')':
|
||||
startIndex -= endIndex - startIndex;
|
||||
break;
|
||||
default:
|
||||
for (pathIndex = startIndex; startIndex <= endIndex ? pathIndex < endIndex : pathIndex > endIndex; startIndex <= endIndex ? pathIndex++ : pathIndex--) {
|
||||
paths[pathIndex] += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
paths.reverse();
|
||||
return paths;
|
||||
};
|
||||
Sherpa.prototype.url = function(name, params) {
|
||||
var _ref;
|
||||
return (_ref = this.routes[name]) != null ? _ref.url(params) : void 0;
|
||||
};
|
||||
Sherpa.prototype.addComplexPart = function(subparts, compiledPath, matchesWith, variableNames) {
|
||||
var captures, capturingIndicies, escapeRegexp, name, part, regexSubparts, regexp, spans, splittingIndicies, _ref;
|
||||
escapeRegexp = function(str) {
|
||||
return str.replace(/([\.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
|
||||
};
|
||||
_ref = [[], [], 0, false], capturingIndicies = _ref[0], splittingIndicies = _ref[1], captures = _ref[2], spans = _ref[3];
|
||||
regexSubparts = (function() {
|
||||
var _i, _len, _results;
|
||||
_results = [];
|
||||
for (_i = 0, _len = subparts.length; _i < _len; _i++) {
|
||||
part = subparts[_i];
|
||||
_results.push((function() {
|
||||
var _ref2;
|
||||
switch (part[0]) {
|
||||
case '\\':
|
||||
compiledPath.push("'" + part[1] + "'");
|
||||
return escapeRegexp(part[1]);
|
||||
case ':':
|
||||
case '*':
|
||||
if (part[0] === '*') {
|
||||
spans = true;
|
||||
}
|
||||
captures += 1;
|
||||
name = part.slice(1, part.length);
|
||||
variableNames.push(name);
|
||||
if (part[0] === '*') {
|
||||
splittingIndicies.push(captures);
|
||||
compiledPath.push("params['" + name + "'].join('/')");
|
||||
} else {
|
||||
capturingIndicies.push(captures);
|
||||
compiledPath.push("params['" + name + "']");
|
||||
}
|
||||
if (spans) {
|
||||
if (matchesWith[name] != null) {
|
||||
return "((?:" + matchesWith[name].source + "\\/?)+)";
|
||||
} else {
|
||||
return '(.*?)';
|
||||
}
|
||||
} else {
|
||||
return "(" + (((_ref2 = matchesWith[name]) != null ? _ref2.source : void 0) || '[^/]*?') + ")";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
compiledPath.push("'" + part + "'");
|
||||
return escapeRegexp(part);
|
||||
}
|
||||
})());
|
||||
}
|
||||
return _results;
|
||||
})();
|
||||
regexp = new RegExp("" + (regexSubparts.join('')) + "$");
|
||||
if (spans) {
|
||||
return new SpanningRegexMatcher(regexp, capturingIndicies, splittingIndicies);
|
||||
} else {
|
||||
return new RegexMatcher(regexp, capturingIndicies, splittingIndicies);
|
||||
}
|
||||
};
|
||||
Sherpa.prototype.addSimplePart = function(subparts, compiledPath, matchesWith, variableNames) {
|
||||
var part, variableName;
|
||||
part = subparts[0];
|
||||
switch (part[0]) {
|
||||
case ':':
|
||||
variableName = part.slice(1, part.length);
|
||||
compiledPath.push("params['" + variableName + "']");
|
||||
variableNames.push(variableName);
|
||||
if (matchesWith[variableName] != null) {
|
||||
return new SpanningRegexMatcher(matchesWith[variableName], [0], []);
|
||||
} else {
|
||||
return new Variable();
|
||||
}
|
||||
break;
|
||||
case '*':
|
||||
compiledPath.push("params['" + variableName + "'].join('/')");
|
||||
variableName = part.slice(1, part.length);
|
||||
variableNames.push(variableName);
|
||||
return new Glob(matchesWith[variableName]);
|
||||
default:
|
||||
compiledPath.push("'" + part + "'");
|
||||
return new Lookup(part);
|
||||
}
|
||||
};
|
||||
Sherpa.prototype.add = function(rawPath, opts) {
|
||||
var compiledPath, defaults, matchesWith, nextNodeFn, node, part, partiallyMatch, parts, path, pathSet, route, routeName, subparts, variableNames;
|
||||
matchesWith = (opts != null ? opts.matchesWith : void 0) || {};
|
||||
defaults = (opts != null ? opts["default"] : void 0) || {};
|
||||
routeName = opts != null ? opts.name : void 0;
|
||||
partiallyMatch = false;
|
||||
route = rawPath.exec != null ? new Route([this.root.add(new RegexPath(this.root, rawPath))]) : (rawPath.substring(rawPath.length - 1) === '*' ? (rawPath = rawPath.substring(0, rawPath.length - 1), partiallyMatch = true) : void 0, pathSet = (function() {
|
||||
var _i, _j, _len, _len2, _ref, _results;
|
||||
_ref = this.generatePaths(rawPath);
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
path = _ref[_i];
|
||||
node = this.root;
|
||||
variableNames = [];
|
||||
parts = path.split('/');
|
||||
compiledPath = [];
|
||||
for (_j = 0, _len2 = parts.length; _j < _len2; _j++) {
|
||||
part = parts[_j];
|
||||
if (part !== '') {
|
||||
compiledPath.push("'/'");
|
||||
subparts = this.findSubparts(part);
|
||||
nextNodeFn = subparts.length === 1 ? this.addSimplePart : this.addComplexPart;
|
||||
node = node.add(nextNodeFn(subparts, compiledPath, matchesWith, variableNames));
|
||||
}
|
||||
}
|
||||
if ((opts != null ? opts.conditions : void 0) != null) {
|
||||
node = node.add(new RequestMatcher(opts.conditions));
|
||||
}
|
||||
path = new Path(node, variableNames);
|
||||
path.partial = partiallyMatch;
|
||||
path.compiled = compiledPath.length === 0 ? "'/'" : compiledPath.join('+');
|
||||
_results.push(path);
|
||||
}
|
||||
return _results;
|
||||
}).call(this), new Route(pathSet, matchesWith));
|
||||
route["default"] = defaults;
|
||||
route.name = routeName;
|
||||
if (routeName != null) {
|
||||
this.routes[routeName] = route;
|
||||
}
|
||||
return route;
|
||||
};
|
||||
Response = (function() {
|
||||
function Response(request, httpResponse, position) {
|
||||
this.request = request;
|
||||
this.httpResponse = httpResponse;
|
||||
this.position = position;
|
||||
this.position || (this.position = 0);
|
||||
}
|
||||
Response.prototype.next = function() {
|
||||
if (this.position === this.destinations.length - 1) {
|
||||
return false;
|
||||
} else {
|
||||
return new Response(this.request, this.httpResponse, this.position + 1).invoke();
|
||||
}
|
||||
};
|
||||
Response.prototype.invoke = function() {
|
||||
var req;
|
||||
req = typeof this.request.underlyingRequest === 'string' ? {} : this.request.underlyingRequest;
|
||||
req.params = this.request.destinations[this.position].params;
|
||||
req.route = this.request.destinations[this.position].route;
|
||||
req.pathInfo = this.request.destinations[this.position].pathInfo;
|
||||
return this.request.destinations[this.position].route.destination(req, this.httpResponse);
|
||||
};
|
||||
return Response;
|
||||
})();
|
||||
Node = (function() {
|
||||
function Node() {
|
||||
this.type || (this.type = 'node');
|
||||
this.matchers = [];
|
||||
}
|
||||
Node.prototype.add = function(n) {
|
||||
var _ref;
|
||||
if (!((_ref = this.matchers[this.matchers.length - 1]) != null ? _ref.usable(n) : void 0)) {
|
||||
this.matchers.push(n);
|
||||
}
|
||||
return this.matchers[this.matchers.length - 1].use(n);
|
||||
};
|
||||
Node.prototype.usable = function(n) {
|
||||
return n.type === this.type;
|
||||
};
|
||||
Node.prototype.match = function(request) {
|
||||
var m, _i, _len, _ref, _results;
|
||||
_ref = this.matchers;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
m = _ref[_i];
|
||||
_results.push(m.match(request));
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
Node.prototype.superMatch = Node.prototype.match;
|
||||
Node.prototype.use = function(n) {
|
||||
return this;
|
||||
};
|
||||
return Node;
|
||||
})();
|
||||
Lookup = (function() {
|
||||
__extends(Lookup, Node);
|
||||
function Lookup(part) {
|
||||
this.part = part;
|
||||
this.type = 'lookup';
|
||||
this.map = {};
|
||||
Lookup.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
Lookup.prototype.match = function(request) {
|
||||
var part;
|
||||
if (this.map[request.path[0]] != null) {
|
||||
request = request.clone();
|
||||
part = request.path.shift();
|
||||
return this.map[part].match(request);
|
||||
}
|
||||
};
|
||||
Lookup.prototype.use = function(n) {
|
||||
var _base, _name;
|
||||
(_base = this.map)[_name = n.part] || (_base[_name] = new Node());
|
||||
return this.map[n.part];
|
||||
};
|
||||
return Lookup;
|
||||
})();
|
||||
Variable = (function() {
|
||||
__extends(Variable, Node);
|
||||
function Variable() {
|
||||
this.type || (this.type = 'variable');
|
||||
Variable.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
Variable.prototype.match = function(request) {
|
||||
if (request.path.length > 0) {
|
||||
request = request.clone();
|
||||
request.variables.push(request.path.shift());
|
||||
return Variable.__super__.match.call(this, request);
|
||||
}
|
||||
};
|
||||
return Variable;
|
||||
})();
|
||||
Glob = (function() {
|
||||
__extends(Glob, Variable);
|
||||
function Glob(regexp) {
|
||||
this.regexp = regexp;
|
||||
this.type = 'glob';
|
||||
Glob.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
Glob.prototype.match = function(request) {
|
||||
var cloned_path, i, match, original_request, _ref, _results;
|
||||
if (request.path.length > 0) {
|
||||
original_request = request;
|
||||
cloned_path = request.path.slice(0, request.path);
|
||||
_results = [];
|
||||
for (i = 1, _ref = original_request.path.length; 1 <= _ref ? i <= _ref : i >= _ref; 1 <= _ref ? i++ : i--) {
|
||||
request = original_request.clone();
|
||||
if (this.regexp != null) {
|
||||
match = request.path[i - 1].match(this.regexp);
|
||||
}
|
||||
if ((this.regexp != null) && (!(match != null) || match[0].length !== request.path[i - 1].length)) {
|
||||
return;
|
||||
}
|
||||
request.variables.push(request.path.slice(0, i));
|
||||
request.path = request.path.slice(i, request.path.length);
|
||||
_results.push(this.superMatch(request));
|
||||
}
|
||||
return _results;
|
||||
}
|
||||
};
|
||||
return Glob;
|
||||
})();
|
||||
RegexMatcher = (function() {
|
||||
__extends(RegexMatcher, Node);
|
||||
function RegexMatcher(regexp, capturingIndicies, splittingIndicies) {
|
||||
var i, _i, _j, _len, _len2, _ref, _ref2;
|
||||
this.regexp = regexp;
|
||||
this.capturingIndicies = capturingIndicies;
|
||||
this.splittingIndicies = splittingIndicies;
|
||||
this.type || (this.type = 'regex');
|
||||
this.varIndicies = [];
|
||||
_ref = this.splittingIndicies;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
i = _ref[_i];
|
||||
this.varIndicies[i] = [i, 'split'];
|
||||
}
|
||||
_ref2 = this.capturingIndicies;
|
||||
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
|
||||
i = _ref2[_j];
|
||||
this.varIndicies[i] = [i, 'capture'];
|
||||
}
|
||||
this.varIndicies.sort(function(a, b) {
|
||||
return a[0] - b[0];
|
||||
});
|
||||
RegexMatcher.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
RegexMatcher.prototype.match = function(request) {
|
||||
var match;
|
||||
if ((request.path[0] != null) && (match = request.path[0].match(this.regexp))) {
|
||||
if (match[0].length !== request.path[0].length) {
|
||||
return;
|
||||
}
|
||||
request = request.clone();
|
||||
this.addVariables(request, match);
|
||||
request.path.shift();
|
||||
return RegexMatcher.__super__.match.call(this, request);
|
||||
}
|
||||
};
|
||||
RegexMatcher.prototype.addVariables = function(request, match) {
|
||||
var idx, type, v, _i, _len, _ref, _results;
|
||||
_ref = this.varIndicies;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
v = _ref[_i];
|
||||
if (v != null) {
|
||||
idx = v[0];
|
||||
type = v[1];
|
||||
_results.push((function() {
|
||||
switch (type) {
|
||||
case 'split':
|
||||
return request.variables.push(match[idx].split('/'));
|
||||
case 'capture':
|
||||
return request.variables.push(match[idx]);
|
||||
}
|
||||
})());
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
RegexMatcher.prototype.usable = function(n) {
|
||||
return n.type === this.type && n.regexp === this.regexp && n.capturingIndicies === this.capturingIndicies && n.splittingIndicies === this.splittingIndicies;
|
||||
};
|
||||
return RegexMatcher;
|
||||
})();
|
||||
SpanningRegexMatcher = (function() {
|
||||
__extends(SpanningRegexMatcher, RegexMatcher);
|
||||
function SpanningRegexMatcher(regexp, capturingIndicies, splittingIndicies) {
|
||||
this.regexp = regexp;
|
||||
this.capturingIndicies = capturingIndicies;
|
||||
this.splittingIndicies = splittingIndicies;
|
||||
this.type = 'spanning';
|
||||
SpanningRegexMatcher.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
SpanningRegexMatcher.prototype.match = function(request) {
|
||||
var match, wholePath;
|
||||
if (request.path.length > 0) {
|
||||
wholePath = request.wholePath();
|
||||
if (match = wholePath.match(this.regexp)) {
|
||||
if (match.index !== 0) {
|
||||
return;
|
||||
}
|
||||
request = request.clone();
|
||||
this.addVariables(request, match);
|
||||
request.path = request.splitPath(wholePath.slice(match.index + match[0].length, wholePath.length));
|
||||
return this.superMatch(request);
|
||||
}
|
||||
}
|
||||
};
|
||||
return SpanningRegexMatcher;
|
||||
})();
|
||||
RequestMatcher = (function() {
|
||||
__extends(RequestMatcher, Node);
|
||||
function RequestMatcher(conditions) {
|
||||
this.conditions = conditions;
|
||||
this.type = 'request';
|
||||
RequestMatcher.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
RequestMatcher.prototype.match = function(request) {
|
||||
var conditionCount, matcher, matching, satisfiedConditionCount, type, v, val, _ref;
|
||||
conditionCount = 0;
|
||||
satisfiedConditionCount = 0;
|
||||
_ref = this.conditions;
|
||||
for (type in _ref) {
|
||||
matcher = _ref[type];
|
||||
val = request.underlyingRequest[type];
|
||||
conditionCount++;
|
||||
v = matcher instanceof Array ? (matching = function() {
|
||||
var cond, _i, _len;
|
||||
for (_i = 0, _len = matcher.length; _i < _len; _i++) {
|
||||
cond = matcher[_i];
|
||||
if (cond.exec != null) {
|
||||
if (matcher.exec(val)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (cond === val) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, matching()) : matcher.exec != null ? matcher.exec(val) : matcher === val;
|
||||
if (v) {
|
||||
satisfiedConditionCount++;
|
||||
}
|
||||
}
|
||||
if (conditionCount === satisfiedConditionCount) {
|
||||
return RequestMatcher.__super__.match.call(this, request);
|
||||
}
|
||||
};
|
||||
RequestMatcher.prototype.usable = function(n) {
|
||||
return n.type === this.type && n.conditions === this.conditions;
|
||||
};
|
||||
return RequestMatcher;
|
||||
})();
|
||||
Path = (function() {
|
||||
__extends(Path, Node);
|
||||
function Path(parent, variableNames) {
|
||||
this.parent = parent;
|
||||
this.variableNames = variableNames;
|
||||
this.type = 'path';
|
||||
this.partial = false;
|
||||
}
|
||||
Path.prototype.addDestination = function(request) {
|
||||
return request.destinations.push({
|
||||
route: this.route,
|
||||
request: request,
|
||||
params: this.constructParams(request)
|
||||
});
|
||||
};
|
||||
Path.prototype.match = function(request) {
|
||||
if (this.partial || request.path.length === 0) {
|
||||
this.addDestination(request);
|
||||
if (this.partial) {
|
||||
return request.destinations[request.destinations.length - 1].pathInfo = "/" + (request.wholePath());
|
||||
}
|
||||
}
|
||||
};
|
||||
Path.prototype.constructParams = function(request) {
|
||||
var i, params, _ref;
|
||||
params = {};
|
||||
for (i = 0, _ref = this.variableNames.length; 0 <= _ref ? i < _ref : i > _ref; 0 <= _ref ? i++ : i--) {
|
||||
params[this.variableNames[i]] = request.variables[i];
|
||||
}
|
||||
return params;
|
||||
};
|
||||
Path.prototype.url = function(rawParams) {
|
||||
var key, match, name, params, path, _i, _j, _k, _len, _len2, _len3, _ref, _ref2, _ref3;
|
||||
if (rawParams == null) {
|
||||
rawParams = {};
|
||||
}
|
||||
params = {};
|
||||
_ref = this.variableNames;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
key = _ref[_i];
|
||||
params[key] = this.route["default"] != null ? rawParams[key] || this.route["default"][key] : rawParams[key];
|
||||
if (!(params[key] != null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ref2 = this.variableNames;
|
||||
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
|
||||
name = _ref2[_j];
|
||||
if (this.route.matchesWith[name] != null) {
|
||||
match = params[name].match(this.route.matchesWith[name]);
|
||||
if (!((match != null) && match[0].length === params[name].length)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
path = this.compiled === '' ? '' : eval(this.compiled);
|
||||
if (path != null) {
|
||||
_ref3 = this.variableNames;
|
||||
for (_k = 0, _len3 = _ref3.length; _k < _len3; _k++) {
|
||||
name = _ref3[_k];
|
||||
delete rawParams[name];
|
||||
}
|
||||
return path;
|
||||
}
|
||||
};
|
||||
return Path;
|
||||
})();
|
||||
RegexPath = (function() {
|
||||
__extends(RegexPath, Path);
|
||||
function RegexPath(parent, regexp) {
|
||||
this.parent = parent;
|
||||
this.regexp = regexp;
|
||||
this.type = 'regexp_route';
|
||||
RegexPath.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
RegexPath.prototype.match = function(request) {
|
||||
request.regexpRouteMatch = this.regexp.exec(request.decodedPath());
|
||||
if ((request.regexpRouteMatch != null) && request.regexpRouteMatch[0].length === request.decodedPath().length) {
|
||||
request = request.clone();
|
||||
request.path = [];
|
||||
return RegexPath.__super__.match.call(this, request);
|
||||
}
|
||||
};
|
||||
RegexPath.prototype.constructParams = function(request) {
|
||||
return request.regexpRouteMatch;
|
||||
};
|
||||
RegexPath.prototype.url = function(rawParams) {
|
||||
throw "This route cannot be generated";
|
||||
};
|
||||
return RegexPath;
|
||||
})();
|
||||
Route = (function() {
|
||||
function Route(pathSet, matchesWith) {
|
||||
var path, _i, _len, _ref;
|
||||
this.pathSet = pathSet;
|
||||
this.matchesWith = matchesWith;
|
||||
_ref = this.pathSet;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
path = _ref[_i];
|
||||
path.route = this;
|
||||
}
|
||||
}
|
||||
Route.prototype.to = function(destination) {
|
||||
var path, _i, _len, _ref, _results;
|
||||
this.destination = destination;
|
||||
_ref = this.pathSet;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
path = _ref[_i];
|
||||
_results.push(path.parent.add(path));
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
Route.prototype.generateQuery = function(params, base, query) {
|
||||
var idx, k, v, _ref;
|
||||
query = "";
|
||||
base || (base = "");
|
||||
if (params != null) {
|
||||
if (params instanceof Array) {
|
||||
for (idx = 0, _ref = params.length; 0 <= _ref ? idx < _ref : idx > _ref; 0 <= _ref ? idx++ : idx--) {
|
||||
query += this.generateQuery(params[idx], "" + base + "[]");
|
||||
}
|
||||
} else if (params instanceof Object) {
|
||||
for (k in params) {
|
||||
v = params[k];
|
||||
query += this.generateQuery(v, base === '' ? k : "" + base + "[" + k + "]");
|
||||
}
|
||||
} else {
|
||||
query += encodeURIComponent(base).replace(/%20/g, '+');
|
||||
query += '=';
|
||||
query += encodeURIComponent(params).replace(/%20/g, '+');
|
||||
query += '&';
|
||||
}
|
||||
}
|
||||
return query;
|
||||
};
|
||||
Route.prototype.url = function(params) {
|
||||
var joiner, path, pathObj, query, _i, _len, _ref;
|
||||
path = void 0;
|
||||
_ref = this.pathSet;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
pathObj = _ref[_i];
|
||||
path = pathObj.url(params);
|
||||
if (path != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (path != null) {
|
||||
query = this.generateQuery(params);
|
||||
joiner = query !== '' ? '?' : '';
|
||||
return "" + (encodeURI(path)) + joiner + (query.substr(0, query.length - 1));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
return Route;
|
||||
})();
|
||||
Request = (function() {
|
||||
function Request(underlyingRequest, callback) {
|
||||
this.underlyingRequest = underlyingRequest;
|
||||
this.callback = callback;
|
||||
this.variables = [];
|
||||
this.destinations = [];
|
||||
if (this.underlyingRequest != null) {
|
||||
this.path = this.splitPath();
|
||||
}
|
||||
}
|
||||
Request.prototype.toString = function() {
|
||||
return "<Request path: /" + (this.path.join('/')) + " " + this.path.length + ">";
|
||||
};
|
||||
Request.prototype.wholePath = function() {
|
||||
return this.path.join('/');
|
||||
};
|
||||
Request.prototype.decodedPath = function(path) {
|
||||
if (path == null) {
|
||||
path = require('url').parse(this.underlyingRequest.url).pathname;
|
||||
}
|
||||
return decodeURI(path);
|
||||
};
|
||||
Request.prototype.splitPath = function(path) {
|
||||
var decodedPath, splitPath;
|
||||
decodedPath = this.decodedPath(path);
|
||||
splitPath = decodedPath === '/' ? [] : decodedPath.split('/');
|
||||
if (splitPath[0] === '') {
|
||||
splitPath.shift();
|
||||
}
|
||||
return splitPath;
|
||||
};
|
||||
Request.prototype.clone = function() {
|
||||
var c;
|
||||
c = new Request();
|
||||
c.path = this.path.slice(0, this.path.length);
|
||||
c.variables = this.variables.slice(0, this.variables.length);
|
||||
c.underlyingRequest = this.underlyingRequest;
|
||||
c.callback = this.callback;
|
||||
c.destinations = this.destinations;
|
||||
return c;
|
||||
};
|
||||
return Request;
|
||||
})();
|
||||
PathRequest = (function() {
|
||||
__extends(PathRequest, Request);
|
||||
function PathRequest() {
|
||||
PathRequest.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
PathRequest.prototype.decodedPath = function(path) {
|
||||
if (path == null) {
|
||||
path = this.underlyingRequest;
|
||||
}
|
||||
return decodeURI(path);
|
||||
};
|
||||
return PathRequest;
|
||||
})();
|
||||
return Sherpa;
|
||||
})();
|
||||
}).call(this);
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "http_router",
|
||||
"description": "URL routing and generation in js",
|
||||
"author": "Joshua Hull <joshbuddy@gmail.com>",
|
||||
"version": "0.0.0",
|
||||
"directories": {
|
||||
"lib" : "./lib/http_router"
|
||||
},
|
||||
"main": "lib/http_router"
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
# About
|
||||
|
||||
Simple test framework for asynchronous testing in [Node.js](http://nodejs.org/). It's trying to be as simple and explicit as possible. No magic, no wheel reinventing. Just use minitest for building your tests and the [assert library](http://nodejs.org/api.html#assert-212) for the actual helpers for testing equality etc.
|
||||
|
||||
This is how the output looks like:
|
||||
|
||||
![Minitest.js output](http://github.com/botanicus/minitest.js/raw/master/minitest.png)
|
||||
|
||||
# Setup
|
||||
|
||||
* `require()` minitest
|
||||
* Use `minitest.setupListeners()` for listening on the `uncaughtException` and `exit` events.
|
||||
* Use `minitest.context(description, block)` for defining your contexts. Context will be usually a function or object name.
|
||||
* Use `#<a Context>.assertion(description, block)` for defining your assertions.
|
||||
* Use `#<a Test>.finished()` to mark test as finished. All the tests has to have it. Without this you won't be able to write solid asynchronous tests, because you can't ask only "is a and b the same?", but also "did the callback run?".
|
||||
* Run `node foo_test.js` to get the results.
|
||||
|
||||
# Example
|
||||
|
||||
var minitest = require("minitest");
|
||||
var assert = require("assert");
|
||||
|
||||
minitest.setupListeners();
|
||||
|
||||
minitest.context("Context#setup()", function () {
|
||||
this.setup(function () {
|
||||
this.user = {name: "Jakub"};
|
||||
});
|
||||
|
||||
this.assertion("it should setup listeners", function (test) {
|
||||
// test something via the standard assert module
|
||||
assert.ok(this.user)
|
||||
|
||||
// mark test as finished
|
||||
test.finished();
|
||||
});
|
||||
|
||||
this.assertion("it should be able to count", function (test) {
|
||||
if (2 !== 4) {
|
||||
// manually fail the test
|
||||
throw new Error("You can't count, can you?");
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
## Formatters
|
||||
|
||||
If you don't like minitest output, you can simply override following methods:
|
||||
|
||||
* `Context.prototype.contextHeader()`
|
||||
* `Test.prototype.reportSuccess()`
|
||||
* `Test.prototype.reportError(error)`
|
||||
* `Test.prototype.reportNotRun()`
|
||||
|
||||
All this methods are supposed to return a string and all these methods have access to `this.description`.
|
||||
|
||||
# Common Problems in Testing Asynchronous Code
|
||||
|
||||
## Exceptions in Callbacks
|
||||
|
||||
Obviously you can't catch errors which occured in callbacks. Consider following:
|
||||
|
||||
try {
|
||||
db.get("botanicus", function (user) {
|
||||
throw new Error("You can't catch me!");
|
||||
});
|
||||
} catch(error) {
|
||||
// you'll never get in here
|
||||
};
|
||||
|
||||
## Testing Exceptions
|
||||
|
||||
this.assertion("should throw an error", function (test) {
|
||||
assert.throws(function () {
|
||||
throw new Error("Error occured!");
|
||||
test.finished();
|
||||
});
|
||||
});
|
||||
|
||||
This obviously can't work, because exception interrupts the anonymous function we are passing as an argument for `assert.throws()`.
|
||||
|
||||
this.assertion("should throw an error", function (test) {
|
||||
assert.throws(function () {
|
||||
throw new Error("Error occured!");
|
||||
});
|
||||
test.finished();
|
||||
});
|
||||
|
||||
This is better, it will at least work, but what if there will be an error in the `assert.throws()` function and it doesn't call the anonymous function?
|
||||
|
||||
this.assertion("should throw an error", function (test) {
|
||||
assert.throws(function () {
|
||||
test.finished();
|
||||
throw new Error("Error occured!");
|
||||
});
|
||||
});
|
||||
|
||||
OK, this is better, `test.finished()` doesn't jump out of the test, so in case that the assertion will fail, we will get the proper result. However it's not perfect, because I can change `test.finished()` in future to actually jump out of the function (I probably won't do that but you can't know) plus if there would be a bug, so `test.finished()` would cause an exception, it would satisfy `assert.throws()` without actually testing the code. Well, you'd probably noticed in other tests, but still.
|
||||
|
||||
Fortunatelly you can specify error class and expected message for `assert.throws()` in this order: `assert.throws(block, error, message)`.
|
|
@ -0,0 +1,9 @@
|
|||
- Fix the unicode issues with Node.js 0.1.90. It does work with Node.js 0.1.33, but in 0.1.90 it just prints first two lines (context description and first successful message) and then don't output anything (but the code is still running and we are even able to inspect these messages which should be outputted via sys.p()).
|
||||
|
||||
Fix this:
|
||||
context("foo", function () {
|
||||
var test = true;
|
||||
this.assertion("bar", function (test) {
|
||||
sys.p(test); // undefined, which is because of the magic with call
|
||||
});
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
Simple module for coloured output for POSIX shells.
|
||||
|
||||
@example
|
||||
colours.bold.green + "OK: " + colours.reset + description
|
||||
*/
|
||||
|
||||
colours = {
|
||||
reset: "\x1B[0m",
|
||||
|
||||
grey: "\x1B[0;30m",
|
||||
red: "\x1B[0;31m",
|
||||
green: "\x1B[0;32m",
|
||||
yellow: "\x1B[0;33m",
|
||||
blue: "\x1B[0;34m",
|
||||
magenta: "\x1B[0;35m",
|
||||
cyan: "\x1B[0;36m",
|
||||
white: "\x1B[0;37m",
|
||||
|
||||
bold: {
|
||||
grey: "\x1B[1;30m",
|
||||
red: "\x1B[1;31m",
|
||||
green: "\x1B[1;32m",
|
||||
yellow: "\x1B[1;33m",
|
||||
blue: "\x1B[1;34m",
|
||||
magenta: "\x1B[1;35m",
|
||||
cyan: "\x1B[1;36m",
|
||||
white: "\x1B[1;37m",
|
||||
}
|
||||
};
|
||||
|
||||
// exports
|
||||
for (colour in colours) {
|
||||
exports[colour] = colours[colour];
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
var assert = require("assert");
|
||||
var minitest = require("./minitest");
|
||||
|
||||
// setup listeners
|
||||
minitest.setupListeners();
|
||||
|
||||
// tests
|
||||
minitest.context("Minitest.js", function () {
|
||||
this.assertion("it should succeed", function (test) {
|
||||
assert.ok(true);
|
||||
test.finished();
|
||||
});
|
||||
|
||||
this.assertion("it should fail", function (test) {
|
||||
assert.ok(null);
|
||||
test.finished();
|
||||
});
|
||||
|
||||
this.assertion("it should not be finished", function (test) {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,158 @@
|
|||
var sys = require("sys");
|
||||
var colours = require("./colours");
|
||||
|
||||
/* suite */
|
||||
function Suite () {
|
||||
this.contexts = [];
|
||||
};
|
||||
|
||||
Suite.prototype.report = function () {
|
||||
var suite = this;
|
||||
this.contexts.forEach(function(context, index) {
|
||||
sys.puts(context.contextHeader());
|
||||
context.report();
|
||||
if (suite.contexts.length === index) {
|
||||
sys.puts("");
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
Suite.prototype.register = function (context) {
|
||||
this.contexts.push(context);
|
||||
};
|
||||
|
||||
// there is only one suite instance
|
||||
var suite = exports.suite = new Suite();
|
||||
|
||||
/* context */
|
||||
function Context (description, block) {
|
||||
this.tests = [];
|
||||
this.block = block;
|
||||
this.description = description;
|
||||
};
|
||||
|
||||
Context.prototype.run = function () {
|
||||
this.block.call(this);
|
||||
};
|
||||
|
||||
Context.prototype.register = function (test) {
|
||||
this.tests.push(test);
|
||||
};
|
||||
|
||||
Context.prototype.report = function () {
|
||||
this.tests.forEach(function (test) {
|
||||
test.report();
|
||||
});
|
||||
};
|
||||
|
||||
/* test */
|
||||
function Test (description, block, setupBlock) {
|
||||
this.description = description;
|
||||
this.block = block;
|
||||
this.setupBlock = setupBlock;
|
||||
};
|
||||
|
||||
Test.prototype.run = function () {
|
||||
try {
|
||||
if (this.setupBlock) {
|
||||
this.setupBlock.call(this);
|
||||
};
|
||||
|
||||
this.block.call(this, this);
|
||||
} catch(error) {
|
||||
this.failed(error);
|
||||
};
|
||||
};
|
||||
|
||||
Test.prototype.finished = function () {
|
||||
this.result = this.reportSuccess();
|
||||
};
|
||||
|
||||
Test.prototype.failed = function (error) {
|
||||
this.result = this.reportError(error);
|
||||
};
|
||||
|
||||
Test.prototype.report = function () {
|
||||
if (this.result) {
|
||||
sys.puts(this.result);
|
||||
} else {
|
||||
sys.puts(this.reportNotFinished());
|
||||
};
|
||||
};
|
||||
|
||||
/* output formatters */
|
||||
Context.prototype.contextHeader = function () {
|
||||
return colours.bold.yellow + "[= " + this.description + " =]" + colours.reset;
|
||||
};
|
||||
|
||||
Test.prototype.reportSuccess = function () {
|
||||
// return colours.bold.green + " ✔ OK: " + colours.reset + this.description;
|
||||
return colours.bold.green + " OK: " + colours.reset + this.description;
|
||||
};
|
||||
|
||||
Test.prototype.reportError = function (error) {
|
||||
var stack = error.stack.replace(/^/, " ");
|
||||
// return colours.bold.red + " ✖ Error: " + colours.reset + this.description + "\n" + stack;
|
||||
return colours.bold.red + " Error: " + colours.reset + this.description + "\n" + stack;
|
||||
};
|
||||
|
||||
Test.prototype.reportNotFinished = function () {
|
||||
// return colours.bold.magenta + " ✖ Didn't finished: " + colours.reset + this.description;
|
||||
return colours.bold.magenta + " Didn't finished: " + colours.reset + this.description;
|
||||
};
|
||||
|
||||
/* DSL */
|
||||
function context (description, block) {
|
||||
var context = new Context(description, block);
|
||||
suite.register(context);
|
||||
context.run();
|
||||
};
|
||||
|
||||
/*
|
||||
Run an example and print if it was successful or not.
|
||||
|
||||
@example
|
||||
minitest.context("setup()", function () {
|
||||
this.assertion("Default value should be 0", function (test) {
|
||||
assert.equal(value, 0);
|
||||
test.finished();
|
||||
});
|
||||
});
|
||||
*/
|
||||
Context.prototype.assertion = function (description, block) {
|
||||
var test = new Test(description, block, this.setupBlock);
|
||||
this.register(test);
|
||||
test.run();
|
||||
};
|
||||
|
||||
Context.prototype.setup = function (block) {
|
||||
this.setupBlock = block;
|
||||
};
|
||||
|
||||
function runAtExit () {
|
||||
process.addListener("exit", function () {
|
||||
suite.report();
|
||||
});
|
||||
};
|
||||
|
||||
function setupUncaughtExceptionListener () {
|
||||
// TODO: is there any way how to get the test instance,
|
||||
// so we could just set test.result, so everything would be
|
||||
// reported properly on the correct place, not in the middle of tests
|
||||
process.addListener("uncaughtException", function (error) {
|
||||
sys.puts(Test.prototype.reportError(error));
|
||||
});
|
||||
};
|
||||
|
||||
function setupListeners () {
|
||||
setupUncaughtExceptionListener();
|
||||
runAtExit();
|
||||
};
|
||||
|
||||
/* exports */
|
||||
exports.Context = Context;
|
||||
exports.Test = Test;
|
||||
exports.context = context;
|
||||
exports.runAtExit = runAtExit;
|
||||
exports.setupUncaughtExceptionListener = setupUncaughtExceptionListener;
|
||||
exports.setupListeners = setupListeners;
|
Binary file not shown.
After Width: | Height: | Size: 135 KiB |
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
var assert = require("assert");
|
||||
var minitest = require("./minitest");
|
||||
|
||||
// setup listeners
|
||||
minitest.setupListeners();
|
||||
|
||||
// tests
|
||||
minitest.context("Namespacing", function () {
|
||||
this.setup(function () {
|
||||
this.user = {name: "Jakub"};
|
||||
});
|
||||
|
||||
this.assertion("instance variable from setup() should exist in assertion", function () {
|
||||
assert.ok(this.user);
|
||||
this.finished();
|
||||
});
|
||||
|
||||
var foobar = true;
|
||||
this.assertion("local variable from context of the current context should exist in assertion", function () {
|
||||
assert.ok(foobar);
|
||||
this.finished();
|
||||
});
|
||||
|
||||
this.foobaz = true;
|
||||
this.assertion("instance variable from context of the current context should not exist in assertion", function () {
|
||||
assert.equal(undefined, this.foobaz);
|
||||
this.finished();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
coffee -c spec/spec_generate.coffee && node spec/spec_generate.js
|
||||
coffee -c spec/spec_recognize.coffee && node spec/spec_recognize.js
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
util = require 'util'
|
||||
require '../lib/sherpa'
|
||||
minitest = require('./minitest.js/minitest')
|
||||
minitest.setupListeners()
|
||||
assert = require('assert')
|
||||
|
||||
minitest.context "Sherpa#generate()", ->
|
||||
@setup ->
|
||||
@router = new Sherpa()
|
||||
|
||||
@assertion "should generate a simple route", (test) ->
|
||||
@router.add('/test', name: 'simple')
|
||||
assert.equal('/test', @router.url('simple'))
|
||||
test.finished()
|
||||
|
||||
@assertion "should generate a route with a variable in it", (test) ->
|
||||
@router.add('/:test', name:'with_variable')
|
||||
assert.equal('/var', @router.url('with_variable', {test: 'var'}))
|
||||
test.finished()
|
||||
|
||||
@assertion "should generate a route with a regex variable in it", (test) ->
|
||||
@router.add('/:test', matchesWith: {test: /asd|qwe|\d+/}, name: 'with_variable')
|
||||
assert.equal(undefined, @router.url('with_variable', {test: 'variable'}))
|
||||
assert.equal(undefined, @router.url('with_variable', {test: '123qwe'}))
|
||||
assert.equal('/123', @router.url('with_variable', {test: '123'}))
|
||||
assert.equal('/qwe', @router.url('with_variable', {test: 'qwe'}))
|
||||
assert.equal('/asd', @router.url('with_variable', {test: 'asd'}))
|
||||
test.finished()
|
||||
|
||||
|
||||
@assertion "should generate a route with a optionals in it", (test) ->
|
||||
@router.add('/(:test)', name:'with_optional')
|
||||
assert.equal('/', @router.url('with_optional'))
|
||||
assert.equal('/hello', @router.url('with_optional', test: 'hello'))
|
||||
test.finished()
|
||||
|
||||
@assertion "should generate a route with nested optionals in it", (test) ->
|
||||
@router.add('/(:test(/:test2))', name: 'with_optional')
|
||||
assert.equal('/', @router.url('with_optional'))
|
||||
assert.equal('/hello', @router.url('with_optional', {test: 'hello'}))
|
||||
assert.equal('/hello/world', @router.url('with_optional', {test: 'hello', test2: 'world'}))
|
||||
assert.equal('/?test2=hello', @router.url('with_optional', {test2: 'hello'}))
|
||||
test.finished();
|
||||
|
||||
@assertion "should generate extra params as a query string after", (test) ->
|
||||
@router.add('/:test', matchesWith: {test: /asd|qwe|\d+/},name:'with_variable')
|
||||
assert.equal('/123?foo=bar', @router.url('with_variable', {test: '123', foo: 'bar'}))
|
||||
test.finished();
|
||||
|
||||
|
||||
@assertion "should escape values in the URI", (test) ->
|
||||
@router.add('/:test', name: 'with_variable')
|
||||
assert.equal('/%5B%20%5D+=-', @router.url('with_variable', {test: '[ ]+=-'}))
|
||||
test.finished()
|
||||
|
||||
@assertion "should escape values in the query string", (test) ->
|
||||
@router.add('/', name:'simple')
|
||||
assert.equal('/?test+and+more=%5B+%5D%2B%3D-', @router.url('simple', {"test and more": '[ ]+=-'}))
|
||||
test.finished()
|
|
@ -0,0 +1,111 @@
|
|||
(function() {
|
||||
var assert, minitest, util;
|
||||
util = require('util');
|
||||
require('../lib/sherpa');
|
||||
minitest = require('./minitest.js/minitest');
|
||||
minitest.setupListeners();
|
||||
assert = require('assert');
|
||||
minitest.context("Sherpa#generate()", function() {
|
||||
this.setup(function() {
|
||||
return this.router = new Sherpa();
|
||||
});
|
||||
this.assertion("should generate a simple route", function(test) {
|
||||
this.router.add('/test', {
|
||||
name: 'simple'
|
||||
});
|
||||
assert.equal('/test', this.router.url('simple'));
|
||||
return test.finished();
|
||||
});
|
||||
this.assertion("should generate a route with a variable in it", function(test) {
|
||||
this.router.add('/:test', {
|
||||
name: 'with_variable'
|
||||
});
|
||||
assert.equal('/var', this.router.url('with_variable', {
|
||||
test: 'var'
|
||||
}));
|
||||
return test.finished();
|
||||
});
|
||||
this.assertion("should generate a route with a regex variable in it", function(test) {
|
||||
this.router.add('/:test', {
|
||||
matchesWith: {
|
||||
test: /asd|qwe|\d+/
|
||||
},
|
||||
name: 'with_variable'
|
||||
});
|
||||
assert.equal(void 0, this.router.url('with_variable', {
|
||||
test: 'variable'
|
||||
}));
|
||||
assert.equal(void 0, this.router.url('with_variable', {
|
||||
test: '123qwe'
|
||||
}));
|
||||
assert.equal('/123', this.router.url('with_variable', {
|
||||
test: '123'
|
||||
}));
|
||||
assert.equal('/qwe', this.router.url('with_variable', {
|
||||
test: 'qwe'
|
||||
}));
|
||||
assert.equal('/asd', this.router.url('with_variable', {
|
||||
test: 'asd'
|
||||
}));
|
||||
return test.finished();
|
||||
});
|
||||
this.assertion("should generate a route with a optionals in it", function(test) {
|
||||
this.router.add('/(:test)', {
|
||||
name: 'with_optional'
|
||||
});
|
||||
assert.equal('/', this.router.url('with_optional'));
|
||||
assert.equal('/hello', this.router.url('with_optional', {
|
||||
test: 'hello'
|
||||
}));
|
||||
return test.finished();
|
||||
});
|
||||
this.assertion("should generate a route with nested optionals in it", function(test) {
|
||||
this.router.add('/(:test(/:test2))', {
|
||||
name: 'with_optional'
|
||||
});
|
||||
assert.equal('/', this.router.url('with_optional'));
|
||||
assert.equal('/hello', this.router.url('with_optional', {
|
||||
test: 'hello'
|
||||
}));
|
||||
assert.equal('/hello/world', this.router.url('with_optional', {
|
||||
test: 'hello',
|
||||
test2: 'world'
|
||||
}));
|
||||
assert.equal('/?test2=hello', this.router.url('with_optional', {
|
||||
test2: 'hello'
|
||||
}));
|
||||
return test.finished();
|
||||
});
|
||||
this.assertion("should generate extra params as a query string after", function(test) {
|
||||
this.router.add('/:test', {
|
||||
matchesWith: {
|
||||
test: /asd|qwe|\d+/
|
||||
},
|
||||
name: 'with_variable'
|
||||
});
|
||||
assert.equal('/123?foo=bar', this.router.url('with_variable', {
|
||||
test: '123',
|
||||
foo: 'bar'
|
||||
}));
|
||||
return test.finished();
|
||||
});
|
||||
this.assertion("should escape values in the URI", function(test) {
|
||||
this.router.add('/:test', {
|
||||
name: 'with_variable'
|
||||
});
|
||||
assert.equal('/%5B%20%5D+=-', this.router.url('with_variable', {
|
||||
test: '[ ]+=-'
|
||||
}));
|
||||
return test.finished();
|
||||
});
|
||||
return this.assertion("should escape values in the query string", function(test) {
|
||||
this.router.add('/', {
|
||||
name: 'simple'
|
||||
});
|
||||
assert.equal('/?test+and+more=%5B+%5D%2B%3D-', this.router.url('simple', {
|
||||
"test and more": '[ ]+=-'
|
||||
}));
|
||||
return test.finished();
|
||||
});
|
||||
});
|
||||
}).call(this);
|
|
@ -0,0 +1,163 @@
|
|||
util = require 'util'
|
||||
require '../lib/sherpa'
|
||||
minitest = require('./minitest.js/minitest')
|
||||
minitest.setupListeners()
|
||||
assert = require('assert')
|
||||
|
||||
minitest.context "Sherpa#recognize()", ->
|
||||
@setup ->
|
||||
@router = new Sherpa()
|
||||
|
||||
@assertion "should recognize a simple route", (test) ->
|
||||
@router.add('/').to (req, params) ->
|
||||
assert.deepEqual {}, params
|
||||
test.finished()
|
||||
@router.match url: '/'
|
||||
|
||||
@assertion "should recognize another simple route ", (test) ->
|
||||
@router.add('/test').to (req, params) ->
|
||||
assert.deepEqual {}, params
|
||||
test.finished()
|
||||
@router.match url: '/test'
|
||||
|
||||
@assertion "should recognize a route with a variable", (test) ->
|
||||
@router.add('/:test').to (req, params) ->
|
||||
assert.deepEqual test: 'variable', params
|
||||
test.finished()
|
||||
@router.match url: '/variable'
|
||||
|
||||
|
||||
@assertion "should recognize a route with an interstitial variable", (test) ->
|
||||
@router.add('/test-:test-test').to (req, params) ->
|
||||
assert.deepEqual {test: 'variable'}, params
|
||||
test.finished()
|
||||
@router.match url: '/test-variable-test'
|
||||
|
||||
|
||||
@assertion "should recognize a route with a variable at the end of the path", (test) ->
|
||||
@router.add('/test/:test').to (req, params) ->
|
||||
assert.deepEqual {test: 'variable'}, params
|
||||
test.finished()
|
||||
@router.match url: '/test/variable'
|
||||
|
||||
@assertion "should recognize a simple route with optionals", (test) ->
|
||||
count = 0
|
||||
@router.add('/(test)').to (req, params) ->
|
||||
assert.deepEqual {}, params
|
||||
test.finished() if count == 1
|
||||
count++
|
||||
@router.match url: '/'
|
||||
@router.match url: '/test'
|
||||
|
||||
@assertion "should recognize a route based on a request method", (test) ->
|
||||
routes = []
|
||||
@router.add('/test', conditions: {method: 'GET'}).to (req, params) ->
|
||||
assert.deepEqual {}, params
|
||||
routes.push 'get'
|
||||
@router.add('/test', conditions: {method: 'POST'}).to (req, params) ->
|
||||
assert.deepEqual {}, params
|
||||
routes.push 'post'
|
||||
@router.add('/test').to (req, params) ->
|
||||
assert.deepEqual {}, params
|
||||
assert.deepEqual ['get', 'post'], routes
|
||||
routes.push 'post'
|
||||
test.finished();
|
||||
@router.match url: '/test', method: 'GET'
|
||||
@router.match url: '/test', method: 'POST'
|
||||
@router.match url: '/test', method: 'PUT'
|
||||
|
||||
@assertion "should recognize a simple route with nested optionals", (test) ->
|
||||
urls = []
|
||||
@router.callback = ->
|
||||
assert.deepEqual ['/test', '/test/test2', '/test/test2/test3'], urls
|
||||
test.finished()
|
||||
@router.add('/test(/test2(/test3))').to (req, params) ->
|
||||
assert.deepEqual {}, params
|
||||
urls.push req.url
|
||||
@router.match url:'/test'
|
||||
@router.match url:'/test/test2'
|
||||
@router.match url:'/test/test2/test3'
|
||||
@router.match url:'/test/test3'
|
||||
|
||||
|
||||
@assertion "should recognize a route based on multiple request keys", (test) ->
|
||||
routes = []
|
||||
@router.add('/test', conditions: {method: 'GET', scheme: 'http' }).to -> routes.push('http-get')
|
||||
@router.add('/test', conditions: {method: 'POST', scheme: 'http' }).to -> routes.push('http-post')
|
||||
@router.add('/test', conditions: {method: 'POST', scheme: 'https'}).to -> routes.push('https-post')
|
||||
@router.add('/test', conditions: {method: 'GET', scheme: 'https'}).to -> routes.push('https-get')
|
||||
@router.add('/test', conditions: { scheme: 'http' }).to -> routes.push('http-any')
|
||||
@router.add('/test', conditions: { scheme: 'https'}).to -> routes.push('https-any')
|
||||
@router.callback = ->
|
||||
assert.deepEqual ['http-post', 'http-get', 'http-any', 'https-get', 'https-post', 'https-any'], routes
|
||||
test.finished()
|
||||
|
||||
@router.match url: '/test', method: 'POST', scheme: 'http'
|
||||
@router.match url: '/test', method: 'GET', scheme: 'http'
|
||||
@router.match url: '/test', method: 'PUT', scheme: 'http'
|
||||
@router.match url: '/test', method: 'GET', scheme: 'https'
|
||||
@router.match url: '/test', method: 'POST', scheme: 'https'
|
||||
@router.match url: '/test', method: 'PUT', scheme: 'https'
|
||||
@router.match url: '/'
|
||||
|
||||
|
||||
@assertion "should recognize a partial route", (test) ->
|
||||
@router.add('/test', partial: true).to -> test.finished()
|
||||
@router.match(url:'/test/testing')
|
||||
|
||||
@assertion "should recognize a route with a regex variable in it", (test) ->
|
||||
vars = ['123', 'qwe', 'asd']
|
||||
missedCount = 0
|
||||
@router.callback = -> missedCount++
|
||||
@router.add('/:test', matchesWith: {test: /asd|qwe|\d+/}).to (req, params) ->
|
||||
assert.equal 2, missedCount
|
||||
assert.deepEqual {test: vars.shift()}, params
|
||||
test.finished() if vars.length == 0
|
||||
|
||||
@router.match(url:'/variable')
|
||||
@router.match(url:'/123qwe')
|
||||
@router.match(url:'/123')
|
||||
@router.match(url:'/qwe')
|
||||
@router.match(url:'/asd')
|
||||
|
||||
@assertion "should distinguish between identical routes where one has a matchesWith", (test) ->
|
||||
params = []
|
||||
nonParams = []
|
||||
@router.add('/:test', matchesWith: {test: /^(asd|qwe|\d+)$/}).to (req, p)->
|
||||
params.push p
|
||||
@router.add('/:test').to (req, p) ->
|
||||
nonParams.push p
|
||||
if params.length == 3 and nonParams.length == 2
|
||||
assert.deepEqual [{test: '123'}, {test:'qwe'}, {test: 'asd'}], params
|
||||
assert.deepEqual [{test: 'poipio'}, {test:'123asd'}], nonParams
|
||||
test.finished()
|
||||
|
||||
@router.match url:'/123'
|
||||
@router.match url:'/qwe'
|
||||
@router.match url:'/asd'
|
||||
@router.match url:'/poipio'
|
||||
@router.match url:'/123asd'
|
||||
|
||||
@assertion "should recognize a route based on a request method", (test) ->
|
||||
routes = []
|
||||
@router.add('/test', conditions:{method: 'GET'}).to -> routes.push('get')
|
||||
@router.add('/test', conditions:{method: 'POST'}).to -> routes.push('post')
|
||||
@router.add('/test').to ->
|
||||
assert.deepEqual(['get', 'post'], routes)
|
||||
test.finished()
|
||||
@router.match(url:'/test', method: 'GET')
|
||||
@router.match(url:'/test', method: 'POST')
|
||||
@router.match(url:'/test', method: 'PUT')
|
||||
|
||||
|
||||
@assertion "should recognize a route based on a request method regex", (test) ->
|
||||
routes = []
|
||||
@router.add('/test', conditions:{method: 'DELETE'}).to -> routes.push('delete')
|
||||
@router.add('/test', conditions:{method: /GET|POST/}).to -> routes.push('get-post')
|
||||
@router.add('/test').to ->
|
||||
assert.deepEqual ['get-post', 'get-post', 'delete'], routes
|
||||
test.finished()
|
||||
@router.match(url:'/test', method: 'GET')
|
||||
@router.match(url:'/test', method: 'POST')
|
||||
@router.match(url:'/test', method: 'DELETE')
|
||||
@router.match(url:'/test', method: 'PUT')
|
|
@ -0,0 +1,397 @@
|
|||
(function() {
|
||||
var assert, minitest, util;
|
||||
util = require('util');
|
||||
require('../lib/sherpa');
|
||||
minitest = require('./minitest.js/minitest');
|
||||
minitest.setupListeners();
|
||||
assert = require('assert');
|
||||
minitest.context("Sherpa#recognize()", function() {
|
||||
this.setup(function() {
|
||||
return this.router = new Sherpa();
|
||||
});
|
||||
this.assertion("should recognize a simple route", function(test) {
|
||||
this.router.add('/').to(function(req, params) {
|
||||
assert.deepEqual({}, params);
|
||||
return test.finished();
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/'
|
||||
});
|
||||
});
|
||||
this.assertion("should recognize another simple route ", function(test) {
|
||||
this.router.add('/test').to(function(req, params) {
|
||||
assert.deepEqual({}, params);
|
||||
return test.finished();
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/test'
|
||||
});
|
||||
});
|
||||
this.assertion("should recognize a route with a variable", function(test) {
|
||||
this.router.add('/:test').to(function(req, params) {
|
||||
assert.deepEqual({
|
||||
test: 'variable'
|
||||
}, params);
|
||||
return test.finished();
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/variable'
|
||||
});
|
||||
});
|
||||
this.assertion("should recognize a route with an interstitial variable", function(test) {
|
||||
this.router.add('/test-:test-test').to(function(req, params) {
|
||||
assert.deepEqual({
|
||||
test: 'variable'
|
||||
}, params);
|
||||
return test.finished();
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/test-variable-test'
|
||||
});
|
||||
});
|
||||
this.assertion("should recognize a route with a variable at the end of the path", function(test) {
|
||||
this.router.add('/test/:test').to(function(req, params) {
|
||||
assert.deepEqual({
|
||||
test: 'variable'
|
||||
}, params);
|
||||
return test.finished();
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/test/variable'
|
||||
});
|
||||
});
|
||||
this.assertion("should recognize a simple route with optionals", function(test) {
|
||||
var count;
|
||||
count = 0;
|
||||
this.router.add('/(test)').to(function(req, params) {
|
||||
assert.deepEqual({}, params);
|
||||
if (count === 1) {
|
||||
test.finished();
|
||||
}
|
||||
return count++;
|
||||
});
|
||||
this.router.match({
|
||||
url: '/'
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/test'
|
||||
});
|
||||
});
|
||||
this.assertion("should recognize a route based on a request method", function(test) {
|
||||
var routes;
|
||||
routes = [];
|
||||
this.router.add('/test', {
|
||||
conditions: {
|
||||
method: 'GET'
|
||||
}
|
||||
}).to(function(req, params) {
|
||||
assert.deepEqual({}, params);
|
||||
return routes.push('get');
|
||||
});
|
||||
this.router.add('/test', {
|
||||
conditions: {
|
||||
method: 'POST'
|
||||
}
|
||||
}).to(function(req, params) {
|
||||
assert.deepEqual({}, params);
|
||||
return routes.push('post');
|
||||
});
|
||||
this.router.add('/test').to(function(req, params) {
|
||||
assert.deepEqual({}, params);
|
||||
assert.deepEqual(['get', 'post'], routes);
|
||||
routes.push('post');
|
||||
return test.finished();
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test',
|
||||
method: 'GET'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test',
|
||||
method: 'POST'
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/test',
|
||||
method: 'PUT'
|
||||
});
|
||||
});
|
||||
this.assertion("should recognize a simple route with nested optionals", function(test) {
|
||||
var urls;
|
||||
urls = [];
|
||||
this.router.callback = function() {
|
||||
assert.deepEqual(['/test', '/test/test2', '/test/test2/test3'], urls);
|
||||
return test.finished();
|
||||
};
|
||||
this.router.add('/test(/test2(/test3))').to(function(req, params) {
|
||||
assert.deepEqual({}, params);
|
||||
return urls.push(req.url);
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test/test2'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test/test2/test3'
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/test/test3'
|
||||
});
|
||||
});
|
||||
this.assertion("should recognize a route based on multiple request keys", function(test) {
|
||||
var routes;
|
||||
routes = [];
|
||||
this.router.add('/test', {
|
||||
conditions: {
|
||||
method: 'GET',
|
||||
scheme: 'http'
|
||||
}
|
||||
}).to(function() {
|
||||
return routes.push('http-get');
|
||||
});
|
||||
this.router.add('/test', {
|
||||
conditions: {
|
||||
method: 'POST',
|
||||
scheme: 'http'
|
||||
}
|
||||
}).to(function() {
|
||||
return routes.push('http-post');
|
||||
});
|
||||
this.router.add('/test', {
|
||||
conditions: {
|
||||
method: 'POST',
|
||||
scheme: 'https'
|
||||
}
|
||||
}).to(function() {
|
||||
return routes.push('https-post');
|
||||
});
|
||||
this.router.add('/test', {
|
||||
conditions: {
|
||||
method: 'GET',
|
||||
scheme: 'https'
|
||||
}
|
||||
}).to(function() {
|
||||
return routes.push('https-get');
|
||||
});
|
||||
this.router.add('/test', {
|
||||
conditions: {
|
||||
scheme: 'http'
|
||||
}
|
||||
}).to(function() {
|
||||
return routes.push('http-any');
|
||||
});
|
||||
this.router.add('/test', {
|
||||
conditions: {
|
||||
scheme: 'https'
|
||||
}
|
||||
}).to(function() {
|
||||
return routes.push('https-any');
|
||||
});
|
||||
this.router.callback = function() {
|
||||
assert.deepEqual(['http-post', 'http-get', 'http-any', 'https-get', 'https-post', 'https-any'], routes);
|
||||
return test.finished();
|
||||
};
|
||||
this.router.match({
|
||||
url: '/test',
|
||||
method: 'POST',
|
||||
scheme: 'http'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test',
|
||||
method: 'GET',
|
||||
scheme: 'http'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test',
|
||||
method: 'PUT',
|
||||
scheme: 'http'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test',
|
||||
method: 'GET',
|
||||
scheme: 'https'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test',
|
||||
method: 'POST',
|
||||
scheme: 'https'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test',
|
||||
method: 'PUT',
|
||||
scheme: 'https'
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/'
|
||||
});
|
||||
});
|
||||
this.assertion("should recognize a partial route", function(test) {
|
||||
this.router.add('/test', {
|
||||
partial: true
|
||||
}).to(function() {
|
||||
return test.finished();
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/test/testing'
|
||||
});
|
||||
});
|
||||
this.assertion("should recognize a route with a regex variable in it", function(test) {
|
||||
var missedCount, vars;
|
||||
vars = ['123', 'qwe', 'asd'];
|
||||
missedCount = 0;
|
||||
this.router.callback = function() {
|
||||
return missedCount++;
|
||||
};
|
||||
this.router.add('/:test', {
|
||||
matchesWith: {
|
||||
test: /asd|qwe|\d+/
|
||||
}
|
||||
}).to(function(req, params) {
|
||||
assert.equal(2, missedCount);
|
||||
assert.deepEqual({
|
||||
test: vars.shift()
|
||||
}, params);
|
||||
if (vars.length === 0) {
|
||||
return test.finished();
|
||||
}
|
||||
});
|
||||
this.router.match({
|
||||
url: '/variable'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/123qwe'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/123'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/qwe'
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/asd'
|
||||
});
|
||||
});
|
||||
this.assertion("should distinguish between identical routes where one has a matchesWith", function(test) {
|
||||
var nonParams, params;
|
||||
params = [];
|
||||
nonParams = [];
|
||||
this.router.add('/:test', {
|
||||
matchesWith: {
|
||||
test: /^(asd|qwe|\d+)$/
|
||||
}
|
||||
}).to(function(req, p) {
|
||||
return params.push(p);
|
||||
});
|
||||
this.router.add('/:test').to(function(req, p) {
|
||||
nonParams.push(p);
|
||||
if (params.length === 3 && nonParams.length === 2) {
|
||||
assert.deepEqual([
|
||||
{
|
||||
test: '123'
|
||||
}, {
|
||||
test: 'qwe'
|
||||
}, {
|
||||
test: 'asd'
|
||||
}
|
||||
], params);
|
||||
assert.deepEqual([
|
||||
{
|
||||
test: 'poipio'
|
||||
}, {
|
||||
test: '123asd'
|
||||
}
|
||||
], nonParams);
|
||||
return test.finished();
|
||||
}
|
||||
});
|
||||
this.router.match({
|
||||
url: '/123'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/qwe'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/asd'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/poipio'
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/123asd'
|
||||
});
|
||||
});
|
||||
this.assertion("should recognize a route based on a request method", function(test) {
|
||||
var routes;
|
||||
routes = [];
|
||||
this.router.add('/test', {
|
||||
conditions: {
|
||||
method: 'GET'
|
||||
}
|
||||
}).to(function() {
|
||||
return routes.push('get');
|
||||
});
|
||||
this.router.add('/test', {
|
||||
conditions: {
|
||||
method: 'POST'
|
||||
}
|
||||
}).to(function() {
|
||||
return routes.push('post');
|
||||
});
|
||||
this.router.add('/test').to(function() {
|
||||
assert.deepEqual(['get', 'post'], routes);
|
||||
return test.finished();
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test',
|
||||
method: 'GET'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test',
|
||||
method: 'POST'
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/test',
|
||||
method: 'PUT'
|
||||
});
|
||||
});
|
||||
return this.assertion("should recognize a route based on a request method regex", function(test) {
|
||||
var routes;
|
||||
routes = [];
|
||||
this.router.add('/test', {
|
||||
conditions: {
|
||||
method: 'DELETE'
|
||||
}
|
||||
}).to(function() {
|
||||
return routes.push('delete');
|
||||
});
|
||||
this.router.add('/test', {
|
||||
conditions: {
|
||||
method: /GET|POST/
|
||||
}
|
||||
}).to(function() {
|
||||
return routes.push('get-post');
|
||||
});
|
||||
this.router.add('/test').to(function() {
|
||||
assert.deepEqual(['get-post', 'get-post', 'delete'], routes);
|
||||
return test.finished();
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test',
|
||||
method: 'GET'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test',
|
||||
method: 'POST'
|
||||
});
|
||||
this.router.match({
|
||||
url: '/test',
|
||||
method: 'DELETE'
|
||||
});
|
||||
return this.router.match({
|
||||
url: '/test',
|
||||
method: 'PUT'
|
||||
});
|
||||
});
|
||||
});
|
||||
}).call(this);
|
|
@ -0,0 +1,136 @@
|
|||
require.paths.push("#{__dirname}/../lib")
|
||||
fs = require('fs')
|
||||
util = require('util')
|
||||
sys = require('sys')
|
||||
http_router = require('http_router')
|
||||
assert = require('assert')
|
||||
|
||||
class Example
|
||||
constructor: (@routes, @tests) ->
|
||||
|
||||
class Test
|
||||
constructor: (file)->
|
||||
@examples = []
|
||||
contents = fs.readFileSync(file, 'utf8')
|
||||
lines = contents.split(/\n/m)
|
||||
currentTest = null
|
||||
routes = []
|
||||
tests = []
|
||||
for line in lines
|
||||
if line.match(/^#/)
|
||||
# this is a comment, skip
|
||||
else if line.match(/^\s*$/)
|
||||
# empty line, skip
|
||||
else if line.match(/^( |\t)/)
|
||||
# this is a test
|
||||
tests.push(JSON.parse(line))
|
||||
else
|
||||
# this is a route
|
||||
if tests.length != 0
|
||||
@examples.push new Example(routes, tests)
|
||||
routes = []
|
||||
tests = []
|
||||
parsedRoutes = JSON.parse(line)
|
||||
if parsedRoutes instanceof Array
|
||||
for r in parsedRoutes
|
||||
routes.push(r)
|
||||
else
|
||||
routes.push(parsedRoutes)
|
||||
@examples.push new Example(routes, tests)
|
||||
interpretValue: (v) ->
|
||||
if v.regex? then new RegExp(v.regex) else v
|
||||
invoke: -> throw("need to implement")
|
||||
constructRouter: (example) ->
|
||||
router = new Sherpa()
|
||||
for route in example.routes
|
||||
for name, vals of route
|
||||
path = null
|
||||
opts = {name: name}
|
||||
if vals.path?
|
||||
path = @interpretValue(vals.path)
|
||||
delete vals.path
|
||||
if vals.conditions?
|
||||
conditions = {}
|
||||
for k, v of vals.conditions
|
||||
switch k
|
||||
when 'request_method' then conditions.method = @interpretValue(v)
|
||||
else conditions[k] = @interpretValue(v)
|
||||
opts.conditions = conditions
|
||||
delete vals.conditions
|
||||
if vals.default?
|
||||
opts.default = vals.default
|
||||
delete vals.default
|
||||
matchesWith = {}
|
||||
for k, v of vals
|
||||
matchesWith[k] = @interpretValue(v)
|
||||
delete vals.k
|
||||
opts.matchesWith = matchesWith
|
||||
else
|
||||
path = @interpretValue(vals)
|
||||
name = "" + name
|
||||
router.add(path, opts).to (req, response) ->
|
||||
response.params = req.params
|
||||
response.end(req.route.name)
|
||||
router
|
||||
|
||||
class GenerationTest extends Test
|
||||
constructor: -> super
|
||||
invoke: ->
|
||||
console.log("Running #{@examples.length} generation tests")
|
||||
for example in @examples
|
||||
process.stdout.write "*"
|
||||
router = @constructRouter(example)
|
||||
for test in example.tests
|
||||
process.stdout.write "."
|
||||
[expectedResult, name, params] = test
|
||||
continue if params? && params instanceof Array
|
||||
continue if name instanceof Object
|
||||
actualResult = router.url(name, params)
|
||||
assert.equal(expectedResult, actualResult)
|
||||
console.log("\nDone!")
|
||||
|
||||
class RecognitionTest extends Test
|
||||
constructor: -> super
|
||||
invoke: ->
|
||||
console.log("Running #{@examples.length} recognition tests")
|
||||
for example in @examples
|
||||
process.stdout.write "*"
|
||||
router = @constructRouter(example)
|
||||
for test in example.tests
|
||||
mockResponse = end: (part) -> @val = part
|
||||
process.stdout.write "."
|
||||
[expectedRouteName, requestingPath, expectedParams] = test
|
||||
mockRequest = {}
|
||||
complex = false
|
||||
if requestingPath.path?
|
||||
complex = true
|
||||
mockRequest.url = requestingPath.path
|
||||
delete requestingPath.path
|
||||
for k, v of requestingPath
|
||||
mockRequest[k] = v
|
||||
else
|
||||
mockRequest.url = requestingPath
|
||||
mockRequest.url = "http://host#{mockRequest.url}" unless mockRequest.url.match(/^http/)
|
||||
router.match(mockRequest, mockResponse)
|
||||
assert.equal(expectedRouteName, mockResponse.val)
|
||||
expectedParams ||= {}
|
||||
mockResponse.params ||= {}
|
||||
pathInfoExcpectation = null
|
||||
if expectedParams.PATH_INFO?
|
||||
pathInfoExcpectation = expectedParams.PATH_INFO
|
||||
delete expectedParams.PATH_INFO
|
||||
assert.equal(pathInfoExcpectation, mockRequest.pathInfo) if pathInfoExcpectation
|
||||
assert.deepEqual(expectedParams, mockResponse.params)
|
||||
unless complex
|
||||
mockResponse = end: (part) -> @val = part
|
||||
router.match(requestingPath, mockResponse)
|
||||
assert.equal(expectedRouteName, mockResponse.val)
|
||||
expectedParams ||= {}
|
||||
mockResponse.params ||= {}
|
||||
assert.equal(pathInfoExcpectation, mockRequest.pathInfo) if pathInfoExcpectation
|
||||
assert.deepEqual(expectedParams, mockResponse.params)
|
||||
console.log("\nDone!")
|
||||
|
||||
new GenerationTest("#{__dirname}/../../test/common/generate.txt").invoke()
|
||||
new RecognitionTest("#{__dirname}/../../test/common/recognize.txt").invoke()
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
(function() {
|
||||
var Example, GenerationTest, RecognitionTest, Test, assert, fs, http_router, sys, util;
|
||||
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
|
||||
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
|
||||
function ctor() { this.constructor = child; }
|
||||
ctor.prototype = parent.prototype;
|
||||
child.prototype = new ctor;
|
||||
child.__super__ = parent.prototype;
|
||||
return child;
|
||||
};
|
||||
require.paths.push("" + __dirname + "/../lib");
|
||||
fs = require('fs');
|
||||
util = require('util');
|
||||
sys = require('sys');
|
||||
http_router = require('http_router');
|
||||
assert = require('assert');
|
||||
Example = (function() {
|
||||
function Example(routes, tests) {
|
||||
this.routes = routes;
|
||||
this.tests = tests;
|
||||
}
|
||||
return Example;
|
||||
})();
|
||||
Test = (function() {
|
||||
function Test(file) {
|
||||
var contents, currentTest, line, lines, parsedRoutes, r, routes, tests, _i, _j, _len, _len2;
|
||||
this.examples = [];
|
||||
contents = fs.readFileSync(file, 'utf8');
|
||||
lines = contents.split(/\n/m);
|
||||
currentTest = null;
|
||||
routes = [];
|
||||
tests = [];
|
||||
for (_i = 0, _len = lines.length; _i < _len; _i++) {
|
||||
line = lines[_i];
|
||||
if (line.match(/^#/)) {} else if (line.match(/^\s*$/)) {} else if (line.match(/^( |\t)/)) {
|
||||
tests.push(JSON.parse(line));
|
||||
} else {
|
||||
if (tests.length !== 0) {
|
||||
this.examples.push(new Example(routes, tests));
|
||||
routes = [];
|
||||
tests = [];
|
||||
}
|
||||
parsedRoutes = JSON.parse(line);
|
||||
if (parsedRoutes instanceof Array) {
|
||||
for (_j = 0, _len2 = parsedRoutes.length; _j < _len2; _j++) {
|
||||
r = parsedRoutes[_j];
|
||||
routes.push(r);
|
||||
}
|
||||
} else {
|
||||
routes.push(parsedRoutes);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.examples.push(new Example(routes, tests));
|
||||
}
|
||||
Test.prototype.interpretValue = function(v) {
|
||||
if (v.regex != null) {
|
||||
return new RegExp(v.regex);
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
};
|
||||
Test.prototype.invoke = function() {
|
||||
throw "need to implement";
|
||||
};
|
||||
Test.prototype.constructRouter = function(example) {
|
||||
var conditions, k, matchesWith, name, opts, path, route, router, v, vals, _i, _len, _ref, _ref2;
|
||||
router = new Sherpa();
|
||||
_ref = example.routes;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
route = _ref[_i];
|
||||
for (name in route) {
|
||||
vals = route[name];
|
||||
path = null;
|
||||
opts = {
|
||||
name: name
|
||||
};
|
||||
if (vals.path != null) {
|
||||
path = this.interpretValue(vals.path);
|
||||
delete vals.path;
|
||||
if (vals.conditions != null) {
|
||||
conditions = {};
|
||||
_ref2 = vals.conditions;
|
||||
for (k in _ref2) {
|
||||
v = _ref2[k];
|
||||
switch (k) {
|
||||
case 'request_method':
|
||||
conditions.method = this.interpretValue(v);
|
||||
break;
|
||||
default:
|
||||
conditions[k] = this.interpretValue(v);
|
||||
}
|
||||
}
|
||||
opts.conditions = conditions;
|
||||
delete vals.conditions;
|
||||
}
|
||||
if (vals["default"] != null) {
|
||||
opts["default"] = vals["default"];
|
||||
delete vals["default"];
|
||||
}
|
||||
matchesWith = {};
|
||||
for (k in vals) {
|
||||
v = vals[k];
|
||||
matchesWith[k] = this.interpretValue(v);
|
||||
delete vals.k;
|
||||
}
|
||||
opts.matchesWith = matchesWith;
|
||||
} else {
|
||||
path = this.interpretValue(vals);
|
||||
}
|
||||
name = "" + name;
|
||||
router.add(path, opts).to(function(req, response) {
|
||||
response.params = req.params;
|
||||
return response.end(req.route.name);
|
||||
});
|
||||
}
|
||||
}
|
||||
return router;
|
||||
};
|
||||
return Test;
|
||||
})();
|
||||
GenerationTest = (function() {
|
||||
__extends(GenerationTest, Test);
|
||||
function GenerationTest() {
|
||||
GenerationTest.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
GenerationTest.prototype.invoke = function() {
|
||||
var actualResult, example, expectedResult, name, params, router, test, _i, _j, _len, _len2, _ref, _ref2;
|
||||
console.log("Running " + this.examples.length + " generation tests");
|
||||
_ref = this.examples;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
example = _ref[_i];
|
||||
process.stdout.write("*");
|
||||
router = this.constructRouter(example);
|
||||
_ref2 = example.tests;
|
||||
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
|
||||
test = _ref2[_j];
|
||||
process.stdout.write(".");
|
||||
expectedResult = test[0], name = test[1], params = test[2];
|
||||
if ((params != null) && params instanceof Array) {
|
||||
continue;
|
||||
}
|
||||
if (name instanceof Object) {
|
||||
continue;
|
||||
}
|
||||
actualResult = router.url(name, params);
|
||||
assert.equal(expectedResult, actualResult);
|
||||
}
|
||||
}
|
||||
return console.log("\nDone!");
|
||||
};
|
||||
return GenerationTest;
|
||||
})();
|
||||
RecognitionTest = (function() {
|
||||
__extends(RecognitionTest, Test);
|
||||
function RecognitionTest() {
|
||||
RecognitionTest.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
RecognitionTest.prototype.invoke = function() {
|
||||
var complex, example, expectedParams, expectedRouteName, k, mockRequest, mockResponse, pathInfoExcpectation, requestingPath, router, test, v, _i, _j, _len, _len2, _ref, _ref2;
|
||||
console.log("Running " + this.examples.length + " recognition tests");
|
||||
_ref = this.examples;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
example = _ref[_i];
|
||||
process.stdout.write("*");
|
||||
router = this.constructRouter(example);
|
||||
_ref2 = example.tests;
|
||||
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
|
||||
test = _ref2[_j];
|
||||
mockResponse = {
|
||||
end: function(part) {
|
||||
return this.val = part;
|
||||
}
|
||||
};
|
||||
process.stdout.write(".");
|
||||
expectedRouteName = test[0], requestingPath = test[1], expectedParams = test[2];
|
||||
mockRequest = {};
|
||||
complex = false;
|
||||
if (requestingPath.path != null) {
|
||||
complex = true;
|
||||
mockRequest.url = requestingPath.path;
|
||||
delete requestingPath.path;
|
||||
for (k in requestingPath) {
|
||||
v = requestingPath[k];
|
||||
mockRequest[k] = v;
|
||||
}
|
||||
} else {
|
||||
mockRequest.url = requestingPath;
|
||||
}
|
||||
if (!mockRequest.url.match(/^http/)) {
|
||||
mockRequest.url = "http://host" + mockRequest.url;
|
||||
}
|
||||
router.match(mockRequest, mockResponse);
|
||||
assert.equal(expectedRouteName, mockResponse.val);
|
||||
expectedParams || (expectedParams = {});
|
||||
mockResponse.params || (mockResponse.params = {});
|
||||
pathInfoExcpectation = null;
|
||||
if (expectedParams.PATH_INFO != null) {
|
||||
pathInfoExcpectation = expectedParams.PATH_INFO;
|
||||
delete expectedParams.PATH_INFO;
|
||||
}
|
||||
if (pathInfoExcpectation) {
|
||||
assert.equal(pathInfoExcpectation, mockRequest.pathInfo);
|
||||
}
|
||||
assert.deepEqual(expectedParams, mockResponse.params);
|
||||
if (!complex) {
|
||||
mockResponse = {
|
||||
end: function(part) {
|
||||
return this.val = part;
|
||||
}
|
||||
};
|
||||
router.match(requestingPath, mockResponse);
|
||||
assert.equal(expectedRouteName, mockResponse.val);
|
||||
expectedParams || (expectedParams = {});
|
||||
mockResponse.params || (mockResponse.params = {});
|
||||
if (pathInfoExcpectation) {
|
||||
assert.equal(pathInfoExcpectation, mockRequest.pathInfo);
|
||||
}
|
||||
assert.deepEqual(expectedParams, mockResponse.params);
|
||||
}
|
||||
}
|
||||
}
|
||||
return console.log("\nDone!");
|
||||
};
|
||||
return RecognitionTest;
|
||||
})();
|
||||
new GenerationTest("" + __dirname + "/../../test/common/generate.txt").invoke();
|
||||
new RecognitionTest("" + __dirname + "/../../test/common/recognize.txt").invoke();
|
||||
}).call(this);
|
|
@ -1,4 +1,4 @@
|
|||
# encoding: utf-8
|
||||
class HttpRouter #:nodoc
|
||||
VERSION = '0.8.11'
|
||||
VERSION = '0.9.0'
|
||||
end
|
|
@ -72,7 +72,8 @@
|
|||
["/1/123", "a", {"entry": "123"}]
|
||||
|
||||
{"a": "/:var"}
|
||||
["/%C3%A4", "a", "ä"]
|
||||
["/%C3%A4", "a", ["ä"]]
|
||||
["/%C3%A4", "a", {"var": "ä"}]
|
||||
|
||||
{"a": {"path": ":var", "var": {"regex": "\\d+"}}}
|
||||
[null, "a", "asd"]
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
[{"router":{"path":"/test", "conditions": {"request_method": "POST"}}}]
|
||||
["router", {"path": "/test", "method": "POST"}]
|
||||
[[405, {"Allow": "POST"}], {"path": "/test", "method": "GET"}]
|
||||
|
||||
{"router": {"path": "/test", "conditions": {"request_method": ["POST", "GET"]}}}
|
||||
["router", {"path": "/test", "method": "POST"}]
|
||||
["router", {"path": "/test", "method": "GET"}]
|
||||
[[405, {"Allow": "GET, POST"}], {"path": "/test", "method": "PUT"}]
|
||||
|
||||
{"get": {"path": "/test(.:format)", "conditions": {"request_method": "GET"}}}
|
||||
{"post": {"path": "/test(.:format)", "conditions": {"request_method": "POST"}}}
|
||||
{"delete": {"path": "/test(.:format)", "conditions": {"request_method": "DELETE"}}}
|
||||
["get", {"path": "/test", "method": "GET"}]
|
||||
["post", {"path": "/test", "method": "POST"}]
|
||||
["delete", {"path": "/test", "method": "DELETE"}]
|
||||
["get", {"path": "/test.html", "method": "GET"}, {"format": "html"}]
|
||||
["post", {"path": "/test.html", "method": "POST"}, {"format": "html"}]
|
||||
["delete", {"path": "/test.html", "method": "DELETE"}, {"format": "html"}]
|
||||
[[405, {"Allow": "DELETE, GET, POST"}], {"path": "/test", "method": "PUT"}]
|
||||
|
||||
{"post": {"path": "/test", "conditions": {"request_method": "POST"}}}
|
||||
{"post_2": {"path": "/test/post", "conditions": {"request_method": "POST"}}}
|
||||
{"get": {"path": "/test", "conditions": {"request_method": "GET"}}}
|
||||
{"get_2": {"path": "/test/post", "conditions": {"request_method": "GET"}}}
|
||||
{"any_2": "/test/post"}
|
||||
{"any": "/test"}
|
||||
["post", {"path": "/test", "method": "POST"}]
|
||||
["get", {"path": "/test", "method": "GET"}]
|
||||
["any", {"path": "/test", "method": "PUT"}]
|
||||
["post_2", {"path": "/test/post", "method": "POST"}]
|
||||
["get_2", {"path": "/test/post", "method": "GET"}]
|
||||
["any_2", {"path": "/test/post", "method": "PUT"}]
|
||||
|
||||
{"post": {"path": "/test", "conditions": {"request_method": "POST"}}}
|
||||
{"any": "/test"}
|
||||
["post", {"path": "/test", "method": "POST"}]
|
||||
["any", {"path": "/test", "method": "PUT"}]
|
||||
|
||||
{"host2_post": {"path": "/test", "conditions": {"request_method": "POST", "host": "host2"}}}
|
||||
{"host2_get": {"path": "/test", "conditions": {"request_method": "GET", "host": "host2"}}}
|
||||
{"host2": {"path": "/test", "conditions": {"host": "host2"}}}
|
||||
{"post": {"path": "/test", "conditions": {"request_method": "POST"}}}
|
||||
["host2", {"path": "http://host2/test", "method": "PUT"}]
|
||||
["post", {"path": "http://host1/test", "method": "POST"}]
|
||||
["host2_get", {"path": "http://host2/test", "method": "GET"}]
|
||||
["host2_post", {"path": "http://host2/test", "method": "POST"}]
|
||||
|
||||
{"with": {"path": "/test", "conditions": {"request_method": "GET", "host": {"regex": "host1"}}}}
|
||||
{"without": {"path": "/test", "conditions": {"request_method": "GET"}}}
|
||||
["without", "http://host2/test"]
|
||||
["with", "http://host2.host1.com/test"]
|
||||
|
||||
{"http": {"path": "/test", "conditions": {"scheme": "http"}}}
|
||||
{"https": {"path": "/test", "conditions": {"scheme": "https"}}}
|
||||
["http", {"path": "/test", "scheme": "http"}]
|
||||
["https", {"path": "/test", "scheme": "https"}]
|
|
@ -38,6 +38,9 @@
|
|||
["route", "/test.html", {"format": "html"}]
|
||||
["route", "/test"]
|
||||
|
||||
{"route": "/"}
|
||||
["route", "/"]
|
||||
|
||||
[{"route": "(.:format)"}]
|
||||
["route", "/.html", {"format": "html"}]
|
||||
["route", "/"]
|
||||
|
@ -49,6 +52,7 @@
|
|||
["route", "/foo", {"test": "foo"}]
|
||||
["route", "/foo.bar", {"format": "bar", "test": "foo"}]
|
||||
|
||||
|
||||
[{"route": {"path": "/:test(.:format)", "format": {"regex": "[^\\.]+"}}}]
|
||||
["route", "/asd@asd.com.json", {"test": "asd@asd.com", "format": "json"}]
|
||||
|
||||
|
@ -176,16 +180,16 @@
|
|||
["test", "/test/optional/", {"PATH_INFO": "/optional/"}]
|
||||
["root", "/testing/optional", {"PATH_INFO": "/testing/optional"}]
|
||||
|
||||
[{"route": "/one-:variable-time"}]
|
||||
["route", "one-value-time", {"variable": "value"}]
|
||||
{"route": "/one-:variable-time"}
|
||||
["route", "/one-value-time", {"variable": "value"}]
|
||||
|
||||
[{"route": {"path": "/one-:variable-time", "variable": {"regex": "\\d+"}}}]
|
||||
["route", "one-123-time", {"variable": "123"}]
|
||||
[null, "one-value-time"]
|
||||
["route", "/one-123-time", {"variable": "123"}]
|
||||
[null, "/one-value-time"]
|
||||
|
||||
[{"route": {"path": "/one-:variable-time", "variable": {"regex": "\\d+"}}}]
|
||||
["route", "one-123-time", {"variable": "123"}]
|
||||
[null, "one-value-time"]
|
||||
["route", "/one-123-time", {"variable": "123"}]
|
||||
[null, "/one-value-time"]
|
||||
|
||||
[{"route": "hey.:greed.html"}]
|
||||
["route", "/hey.greedybody.html", {"greed": "greedybody"}]
|
||||
|
@ -202,62 +206,3 @@
|
|||
{"without_regex": "/:common_variable.:unmatched"}
|
||||
["with_regex", "/common.123", {"common_variable": "common", "matched": "123"}]
|
||||
["without_regex", "/common.other", {"common_variable": "common", "unmatched": "other"}]
|
||||
|
||||
{"nothing":{"path":"/", "conditions": {"request_method": "GET"}}}
|
||||
{"post":{"path":"/test", "conditions": {"request_method": "POST"}}}
|
||||
{"put":{"path":"/test", "conditions": {"request_method": "PUT"}}}
|
||||
["post", {"path": "/test", "method": "POST"}]
|
||||
[[405, {"Allow": "POST, PUT"}], {"path": "/test", "method": "GET"}]
|
||||
|
||||
{"router": {"path": "/test", "conditions": {"request_method": ["POST", "GET"]}}}
|
||||
["router", {"path": "/test", "method": "POST"}]
|
||||
["router", {"path": "/test", "method": "GET"}]
|
||||
[[405, {"Allow": "GET, POST"}], {"path": "/test", "method": "PUT"}]
|
||||
|
||||
{"get": {"path": "/test(.:format)", "conditions": {"request_method": "GET"}}}
|
||||
{"post": {"path": "/test(.:format)", "conditions": {"request_method": "POST"}}}
|
||||
{"delete": {"path": "/test(.:format)", "conditions": {"request_method": "DELETE"}}}
|
||||
["get", {"path": "/test", "method": "GET"}]
|
||||
["post", {"path": "/test", "method": "POST"}]
|
||||
["delete", {"path": "/test", "method": "DELETE"}]
|
||||
["get", {"path": "/test.html", "method": "GET"}, {"format": "html"}]
|
||||
["post", {"path": "/test.html", "method": "POST"}, {"format": "html"}]
|
||||
["delete", {"path": "/test.html", "method": "DELETE"}, {"format": "html"}]
|
||||
[[405, {"Allow": "DELETE, GET, POST"}], {"path": "/test", "method": "PUT"}]
|
||||
|
||||
{"post": {"path": "/test", "conditions": {"request_method": "POST"}}}
|
||||
{"post_2": {"path": "/test/post", "conditions": {"request_method": "POST"}}}
|
||||
{"get": {"path": "/test", "conditions": {"request_method": "GET"}}}
|
||||
{"get_2": {"path": "/test/post", "conditions": {"request_method": "GET"}}}
|
||||
{"any_2": "/test/post"}
|
||||
{"any": "/test"}
|
||||
["post", {"path": "/test", "method": "POST"}]
|
||||
["get", {"path": "/test", "method": "GET"}]
|
||||
["any", {"path": "/test", "method": "PUT"}]
|
||||
["post_2", {"path": "/test/post", "method": "POST"}]
|
||||
["get_2", {"path": "/test/post", "method": "GET"}]
|
||||
["any_2", {"path": "/test/post", "method": "PUT"}]
|
||||
|
||||
{"post": {"path": "/test", "conditions": {"request_method": "POST"}}}
|
||||
{"any": "/test"}
|
||||
["post", {"path": "/test", "method": "POST"}]
|
||||
["any", {"path": "/test", "method": "PUT"}]
|
||||
|
||||
{"host2_post": {"path": "/test", "conditions": {"request_method": "POST", "host": "host2"}}}
|
||||
{"host2_get": {"path": "/test", "conditions": {"request_method": "GET", "host": "host2"}}}
|
||||
{"host2": {"path": "/test", "conditions": {"host": "host2"}}}
|
||||
{"post": {"path": "/test", "conditions": {"request_method": "POST"}}}
|
||||
["host2", {"path": "http://host2/test", "method": "PUT"}]
|
||||
["post", {"path": "http://host1/test", "method": "POST"}]
|
||||
["host2_get", {"path": "http://host2/test", "method": "GET"}]
|
||||
["host2_post", {"path": "http://host2/test", "method": "POST"}]
|
||||
|
||||
{"with": {"path": "/test", "conditions": {"request_method": "GET", "host": {"regex": "host1"}}}}
|
||||
{"without": {"path": "/test", "conditions": {"request_method": "GET"}}}
|
||||
["without", "http://host2/test"]
|
||||
["with", "http://host2.host1.com/test"]
|
||||
|
||||
{"http": {"path": "/test", "conditions": {"scheme": "http"}}}
|
||||
{"https": {"path": "/test", "conditions": {"scheme": "https"}}}
|
||||
["http", {"path": "/test", "scheme": "http"}]
|
||||
["https", {"path": "/test", "scheme": "https"}]
|
||||
|
|
|
@ -1,77 +1,7 @@
|
|||
require 'json'
|
||||
|
||||
generation_file = "#{File.dirname(__FILE__)}/common/generate.txt"
|
||||
generation = File.read(generation_file)
|
||||
|
||||
class GenerationTest
|
||||
Info = Struct.new(:case, :original_line, :num)
|
||||
|
||||
attr_reader :routes, :tests
|
||||
def initialize(file)
|
||||
@tests = []
|
||||
@routes = Info.new([], "", 0)
|
||||
end
|
||||
|
||||
def error(msg)
|
||||
raise("Error in case: #{@routes.original_line.strip}:#{@routes.num + 1}\n#{msg}")
|
||||
end
|
||||
|
||||
def add_test(line, num)
|
||||
@tests << Info.new(JSON.parse(line), line, num)
|
||||
end
|
||||
|
||||
def add_routes(line, num)
|
||||
info = Info.new(JSON.parse(line), line, num)
|
||||
error("Routes have already been defined without tests") if info.case.is_a?(Array) && !@routes.case.empty?
|
||||
if info.case.is_a?(Array)
|
||||
@routes = info
|
||||
elsif @routes.case.empty?
|
||||
info.case = [info.case]
|
||||
@routes = info
|
||||
else
|
||||
@routes.case << info.case
|
||||
end
|
||||
end
|
||||
|
||||
def interpret_val(val)
|
||||
case val
|
||||
when nil
|
||||
error("Unable to interpret #{val.inspect}")
|
||||
when Hash
|
||||
val['regex'] ? Regexp.new(val['regex']) : error("Okay serious, no idea #{val.inspect}")
|
||||
else
|
||||
val
|
||||
end
|
||||
end
|
||||
|
||||
def invoke
|
||||
error("invoke called with no tests or routes") if @tests.empty? || @routes.nil?
|
||||
router = HttpRouter.new
|
||||
@routes.case.each do |route_definition|
|
||||
error("Too many keys! #{route_definition.keys.inspect}") unless route_definition.keys.size == 1
|
||||
route_name, route_properties = route_definition.keys.first, route_definition.values.first
|
||||
route = case route_properties
|
||||
when String
|
||||
router.add(route_properties)
|
||||
when Hash
|
||||
opts = {}
|
||||
route_path = interpret_val(route_properties.delete("path"))
|
||||
if route_properties.key?("conditions")
|
||||
opts[:conditions] = Hash[route_properties.delete("conditions").map{|k, v| [k.to_sym, interpret_val(v)]}]
|
||||
end
|
||||
if route_properties.key?("default")
|
||||
opts[:default_values] = Hash[route_properties.delete("default").map{|k, v| [k.to_sym, interpret_val(v)]}]
|
||||
end
|
||||
route_properties.each do |key, val|
|
||||
opts[key.to_sym] = interpret_val(val)
|
||||
end
|
||||
router.add(route_path, opts)
|
||||
else
|
||||
error("Route isn't a String or hash")
|
||||
end
|
||||
route.name(route_name.to_sym)
|
||||
route.to{|env| [200, {"env-to-test" => env.dup}, [route_name]]}
|
||||
end
|
||||
require "#{File.dirname(__FILE__)}/generic"
|
||||
class GenerationTest < AbstractTest
|
||||
def run_tests
|
||||
@tests.map(&:case).each do |(expected_result, name, args)|
|
||||
args = [args] unless args.is_a?(Array)
|
||||
args.compact!
|
||||
|
@ -89,31 +19,4 @@ class GenerationTest
|
|||
end
|
||||
end
|
||||
|
||||
tests = []
|
||||
test = nil
|
||||
num = 0
|
||||
generation.each_line do |line|
|
||||
begin
|
||||
case line
|
||||
when /^#/, /^\s*$/
|
||||
# skip
|
||||
when /^( |\t)/
|
||||
test.add_test(line, num)
|
||||
else
|
||||
if test.nil? || !test.tests.empty?
|
||||
tests << test if test
|
||||
test = GenerationTest.new(generation_file)
|
||||
end
|
||||
test.add_routes(line, num)
|
||||
end
|
||||
rescue
|
||||
warn "There was a problem with #{num}:#{line}"
|
||||
raise
|
||||
end
|
||||
num += 1
|
||||
end
|
||||
tests << test
|
||||
|
||||
puts "Running generation tests (Routes: #{tests.size}, Tests: #{tests.inject(0){|s, t| s+=t.tests.size}})..."
|
||||
tests.each(&:invoke)
|
||||
puts "\ndone!"
|
||||
GenerationTest.run("#{File.dirname(__FILE__)}/common/generate.txt")
|
|
@ -0,0 +1,110 @@
|
|||
require 'json'
|
||||
|
||||
class AbstractTest
|
||||
def self.run(file)
|
||||
contents = File.read(file)
|
||||
tests = []
|
||||
test = nil
|
||||
num = 0
|
||||
contents.each_line do |line|
|
||||
begin
|
||||
case line
|
||||
when /^#/, /^\s*$/
|
||||
# skip
|
||||
when /^( |\t)/
|
||||
test.add_test(line, num)
|
||||
else
|
||||
if test.nil? || !test.tests.empty?
|
||||
tests << test if test
|
||||
test = new(file)
|
||||
end
|
||||
test.add_routes(line, num)
|
||||
end
|
||||
rescue
|
||||
warn "There was a problem with #{num}:#{line}"
|
||||
raise
|
||||
end
|
||||
num += 1
|
||||
end
|
||||
tests << test
|
||||
|
||||
puts "Running tests (#{name}) (Routes: #{tests.size}, Tests: #{tests.inject(0){|s, t| s+=t.tests.size}})..."
|
||||
tests.each(&:invoke)
|
||||
puts "\ndone!"
|
||||
end
|
||||
|
||||
Info = Struct.new(:case, :original_line, :num)
|
||||
|
||||
attr_reader :routes, :tests
|
||||
def initialize(file)
|
||||
@tests = []
|
||||
@routes = Info.new([], "", 0)
|
||||
end
|
||||
|
||||
def error(msg)
|
||||
raise("Error in case: #{@routes.original_line.strip}:#{@routes.num + 1}\n#{msg}")
|
||||
end
|
||||
|
||||
def add_test(line, num)
|
||||
@tests << Info.new(JSON.parse(line), line, num)
|
||||
end
|
||||
|
||||
def add_routes(line, num)
|
||||
info = Info.new(JSON.parse(line), line, num)
|
||||
error("Routes have already been defined without tests") if info.case.is_a?(Array) && !@routes.case.empty?
|
||||
if info.case.is_a?(Array)
|
||||
@routes = info
|
||||
elsif @routes.case.empty?
|
||||
info.case = [info.case]
|
||||
@routes = info
|
||||
else
|
||||
@routes.case << info.case
|
||||
end
|
||||
end
|
||||
|
||||
def interpret_val(val)
|
||||
case val
|
||||
when nil
|
||||
error("Unable to interpret #{val.inspect}")
|
||||
when Hash
|
||||
val['regex'] ? Regexp.new(val['regex']) : error("Okay serious, no idea #{val.inspect}")
|
||||
else
|
||||
val
|
||||
end
|
||||
end
|
||||
|
||||
def run_tests
|
||||
raise
|
||||
end
|
||||
|
||||
def invoke
|
||||
error("invoke called with no tests or routes") if @tests.empty? || @routes.nil?
|
||||
router = HttpRouter.new
|
||||
@routes.case.each do |route_definition|
|
||||
error("Too many keys! #{route_definition.keys.inspect}") unless route_definition.keys.size == 1
|
||||
route_name, route_properties = route_definition.keys.first, route_definition.values.first
|
||||
route = case route_properties
|
||||
when String
|
||||
router.add(route_properties)
|
||||
when Hash
|
||||
opts = {}
|
||||
route_path = interpret_val(route_properties.delete("path"))
|
||||
if route_properties.key?("conditions")
|
||||
opts[:conditions] = Hash[route_properties.delete("conditions").map{|k, v| [k.to_sym, interpret_val(v)]}]
|
||||
end
|
||||
if route_properties.key?("default")
|
||||
opts[:default_values] = Hash[route_properties.delete("default").map{|k, v| [k.to_sym, interpret_val(v)]}]
|
||||
end
|
||||
route_properties.each do |key, val|
|
||||
opts[key.to_sym] = interpret_val(val)
|
||||
end
|
||||
router.add(route_path, opts)
|
||||
else
|
||||
error("Route isn't a String or hash")
|
||||
end
|
||||
route.name(route_name.to_sym)
|
||||
route.to{|env| [200, {"env-to-test" => env.dup}, [route_name]]}
|
||||
end
|
||||
print '.'
|
||||
end
|
||||
end
|
|
@ -1,142 +1,22 @@
|
|||
require 'json'
|
||||
|
||||
recognition_file = "#{File.dirname(__FILE__)}/common/recognize.txt"
|
||||
recognition = File.read(recognition_file)
|
||||
|
||||
class RecognitionTest
|
||||
Info = Struct.new(:case, :original_line, :num)
|
||||
|
||||
attr_reader :routes, :tests
|
||||
def initialize(file)
|
||||
@tests = []
|
||||
@routes = Info.new([], "", 0)
|
||||
end
|
||||
|
||||
def error(msg)
|
||||
raise("Error in case: #{@routes.original_line.strip}:#{@routes.num + 1}\n#{msg}")
|
||||
end
|
||||
|
||||
def add_test(line, num)
|
||||
@tests << Info.new(JSON.parse(line), line, num)
|
||||
end
|
||||
|
||||
def add_routes(line, num)
|
||||
info = Info.new(JSON.parse(line), line, num)
|
||||
error("Routes have already been defined without tests") if info.case.is_a?(Array) && !@routes.case.empty?
|
||||
if info.case.is_a?(Array)
|
||||
@routes = info
|
||||
elsif @routes.case.empty?
|
||||
info.case = [info.case]
|
||||
@routes = info
|
||||
else
|
||||
@routes.case << info.case
|
||||
end
|
||||
end
|
||||
|
||||
def interpret_val(val)
|
||||
case val
|
||||
when nil
|
||||
error("Unable to interpret #{val.inspect}")
|
||||
when Hash
|
||||
val['regex'] ? Regexp.new(val['regex']) : error("Okay serious, no idea #{val.inspect}")
|
||||
else
|
||||
val
|
||||
end
|
||||
end
|
||||
|
||||
def invoke
|
||||
error("invoke called with no tests or routes") if @tests.empty? || @routes.nil?
|
||||
router = HttpRouter.new
|
||||
@routes.case.each do |route_definition|
|
||||
error("Too many keys! #{route_definition.keys.inspect}") unless route_definition.keys.size == 1
|
||||
route_name, route_properties = route_definition.keys.first, route_definition.values.first
|
||||
route = case route_properties
|
||||
when String
|
||||
router.add(route_properties)
|
||||
when Hash
|
||||
opts = {}
|
||||
route_path = interpret_val(route_properties.delete("path"))
|
||||
if route_properties.key?("conditions")
|
||||
opts[:conditions] = Hash[route_properties.delete("conditions").map{|k, v| [k.to_sym, interpret_val(v)]}]
|
||||
end
|
||||
route_properties.each do |key, val|
|
||||
opts[key.to_sym] = interpret_val(val)
|
||||
end
|
||||
router.add(route_path, opts)
|
||||
else
|
||||
error("Route isn't a String or hash")
|
||||
require "#{File.dirname(__FILE__)}/generic"
|
||||
class RecognitionTest < AbstractTest
|
||||
def run_tests
|
||||
@tests.map(&:case).each do |(expected_result, name, args)|
|
||||
args = [args] unless args.is_a?(Array)
|
||||
args.compact!
|
||||
args.map!{|a| a.is_a?(Hash) ? Hash[a.map{|k,v| [k.to_sym, v]}] : a }
|
||||
result = begin
|
||||
router.url(name.to_sym, *args)
|
||||
rescue HttpRouter::InvalidRouteException
|
||||
nil
|
||||
rescue HttpRouter::MissingParameterException
|
||||
nil
|
||||
end
|
||||
route.name(route_name.to_sym)
|
||||
route.to{|env| [200, {"env-to-test" => env.dup}, [route_name]]}
|
||||
end
|
||||
@tests.map(&:case).each do |(name, req, params)|
|
||||
env = case req
|
||||
when String
|
||||
Rack::MockRequest.env_for(req)
|
||||
when Hash
|
||||
e = Rack::MockRequest.env_for(req['path'])
|
||||
e['REQUEST_METHOD'] = req['method'] if req.key?('method')
|
||||
e['rack.url_scheme'] = req['scheme'] if req.key?('scheme')
|
||||
e
|
||||
end
|
||||
response = router.call(env)
|
||||
case name
|
||||
when nil
|
||||
error("Expected no response") unless response.first == 404
|
||||
when Array
|
||||
name.each_with_index do |part, i|
|
||||
case part
|
||||
when Hash then part.keys.all? or error("#{part.inspect} didn't match #{response[i].inspect}")
|
||||
else part == response[i] or error("#{part.inspect} didn't match #{response[i].inspect}")
|
||||
end
|
||||
end
|
||||
else
|
||||
error("Expected #{name} for #{req.inspect} got #{response.inspect}") unless response.last == [name]
|
||||
end
|
||||
env['router.params'] ||= {}
|
||||
params ||= {}
|
||||
if params['PATH_INFO']
|
||||
path_info = params.delete("PATH_INFO")
|
||||
error("path_info #{env['PATH_INFO'].inspect} is not #{path_info.inspect}") unless path_info == env['PATH_INFO']
|
||||
end
|
||||
|
||||
env['router.params'].keys.each do |k|
|
||||
p_v = params.delete(k.to_s)
|
||||
v = env['router.params'].delete(k.to_sym)
|
||||
error("I got #{p_v.inspect} but expected #{v.inspect}") unless p_v == v
|
||||
end
|
||||
error("Left over expectations: #{params.inspect}") unless params.empty?
|
||||
error("Left over matched params: #{env['router.params'].inspect}") unless env['router.params'].empty?
|
||||
error("Result #{result.inspect} did not match expectation #{expected_result.inspect}") unless result == expected_result
|
||||
end
|
||||
print '.'
|
||||
end
|
||||
end
|
||||
|
||||
tests = []
|
||||
test = nil
|
||||
num = 0
|
||||
recognition.each_line do |line|
|
||||
begin
|
||||
case line
|
||||
when /^#/, /^\s*$/
|
||||
# skip
|
||||
when /^( |\t)/
|
||||
test.add_test(line, num)
|
||||
else
|
||||
if test.nil? || !test.tests.empty?
|
||||
tests << test if test
|
||||
test = RecognitionTest.new(recognition_file)
|
||||
end
|
||||
test.add_routes(line, num)
|
||||
end
|
||||
rescue
|
||||
warn "There was a problem with #{num}:#{line}"
|
||||
raise
|
||||
end
|
||||
num += 1
|
||||
end
|
||||
tests << test
|
||||
|
||||
puts "Running recognition tests (Routes: #{tests.size}, Tests: #{tests.inject(0){|s, t| s+=t.tests.size}})..."
|
||||
tests.each(&:invoke)
|
||||
puts "\ndone!"
|
||||
RecognitionTest.run("#{File.dirname(__FILE__)}/common/recognize.txt")
|
||||
RecognitionTest.run("#{File.dirname(__FILE__)}/common/http_recognize.txt")
|
||||
|
|
Loading…
Reference in New Issue