UNPKG

replay

Version:

When API testing slows you down: record and replay HTTP responses like a boss

103 lines (92 loc) 3.66 kB
# A matcher is a function that, given a request, returns an appropriate response or nothing. # # The most common use case is to calling `Matcher.fromMapping(mapping)`. # # The request consists of: # url - URL object # method - Request method (lower case) # headers - Headers object (names are lower case) # body - Request body (for some requests) # # The response consists of: # version - HTTP version # status - Status code # headers - Headers object (names are lower case) # body - Array of body parts # trailers - Trailers object (names are lower case) assert = require("assert") URL = require("url") # Simple implementation of a matcher. # # To create a matcher from request/response mapping use `fromMapping`. class Matcher constructor: (request, response)-> # Map requests to object properties. We do this for quick matching. assert request.url, "I need at least a URL to match request to response" url = URL.parse(request.url) @hostname = url.hostname @port = url.port @path = url.path @query = url.query @method = (request.method && request.method.toUpperCase()) || "GET" @headers = {} if request.headers for name, value of request.headers @headers[name.toLowerCase()] = value @body = request.body # Create a normalized response object that we return. assert response.status, "I need at least status code to define response" @response = version: response.version || "1.1" status: response.status && parseInt(response.status, 10) headers: {} body: [] trailers: {} # Copy over header to response, downcase header names. if response.headers headers = @response.headers for name, value of response.headers headers[name.toLowerCase()] = value # Copy over body (string) or multiple body parts (strings or content/encoding pairs) if Array.isArray(response.body) @response.body = response.body.slice(0) else if response.body @response.body = [response.body] # Copy over trailers to response, downcase trailers names. if response.trailers trailers = @response.trailers for name, value of response.trailers trailers[name.toLowerCase()] = value # Quick and effective matching. match: (request)-> { url, method, headers, body } = request return false if @hostname && @hostname != url.hostname return false if @port && @port != url.port return false if @path && @path != url.path return false if @query && @query != url.query return false unless @method == method for name, value of @headers return false if value != headers[name] return false if @body && @body != body return true # Returns new matcher function based on the supplied mapping. # # Mapping can contain `request` and `response` object. As shortcut, mapping can specify `path` and `method` (optional) # directly, and also any of the response properties. @fromMapping: (host, mapping)-> assert !!mapping.path ^ !!mapping.request, "Mapping must specify path or request object" if mapping.path request = url: URL.resolve("http://#{host}/", mapping.path) method: mapping.method else request = url: URL.resolve("http://#{host}/", mapping.request.url) method: mapping.request.method headers: mapping.request.headers body: mapping.request.body matcher = new Matcher(request, mapping.response || mapping) return (request)-> if matcher.match(request) return matcher.response module.exports = Matcher