UNPKG

replay

Version:

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

127 lines (104 loc) 4.1 kB
assert = require("assert") File = require("fs") Path = require("path") Matcher = require("./matcher") mkdir = (pathname, callback)-> Path.exists pathname, (exists)-> if exists callback null return parent = Path.dirname(pathname) Path.exists parent, (exists)-> if exists File.mkdir pathname, callback else mkdir parent, -> File.mkdir pathname, callback # Only these request headers are stored in the catalog. REQUEST_HEADERS = [/^accept/, /^content-/, /^host/, /^if-/, /^x-/] class Catalog constructor: (@settings)-> # We use this to cache host/host:port mapped to array of matchers. @matchers = {} find: (host)-> # Return result from cache. matchers = @matchers[host] if matchers return matchers # Start by looking for directory and loading each of the files. pathname = "#{@basedir}/#{host}" unless Path.existsSync(pathname) return stat = File.statSync(pathname) if stat.isDirectory() files = File.readdirSync(pathname) for file in files matchers = @matchers[host] ||= [] mapping = @_read("#{pathname}/#{file}") matchers.push Matcher.fromMapping(host, mapping) else matchers = @matchers[host] ||= [] mapping = @_read(pathname) matchers.push Matcher.fromMapping(host, mapping) return matchers save: (host, request, response, callback)-> matcher = Matcher.fromMapping(host, request: request, response: response) matchers = @matchers[host] ||= [] matchers.push matcher uid = +new Date tmpfile = "/tmp/node-replay.#{uid}" pathname = "#{@basedir}/#{host}" console.log "Creating #{pathname}" mkdir pathname, (error)-> return callback error if error filename = "#{pathname}/#{uid}" try file = File.createWriteStream(tmpfile, encoding: "utf-8") file.write "#{request.method.toUpperCase()} #{request.url.path || "/"}\n" for name, value of request.headers file.write "#{name}: #{value}\n" file.write "\n" # Response part file.write "#{response.status || 200} HTTP/#{response.version || "1.1"}\n" for name, value of response.headers file.write "#{name}: #{value}\n" file.write "\n" for part in response.body file.write part file.end -> File.rename tmpfile, filename, callback callback null catch error callback error @prototype.__defineGetter__ "basedir", -> @_basedir ?= Path.resolve(@settings.fixtures || "fixtures") return @_basedir _read: (filename)-> parse_request = (request)-> assert request, "#{filename} missing request section" [method_and_path, header_lines...] = request.split(/\n/) [method, path] = method_and_path.split(/\s/) assert method && path, "#{filename}: first line must be <method> <path>" headers = parseHeaders(filename, header_lines, REQUEST_HEADERS) return { url: path, method: method, headers: headers } parse_response = (response, body)-> assert response, "#{filename} missing response section" [status_line, header_lines...] = response.split(/\n/) status = status_line.split()[0] version = status_line.match(/\d.\d$/) headers = parseHeaders(filename, header_lines) return { status: status, version: version, headers: headers, body: body.join("\n\n") } [request, response, body...] = File.readFileSync(filename, "utf-8").split(/\n\n/) return { request: parse_request(request), response: parse_response(response, body) } parseHeaders = (filename, header_lines, restrict = null)-> headers = {} for line in header_lines [_, name, value] = line.match(/^(.*?)\:\s+(.*)$/) assert name && value, "#{filename}: can't make sense of header line #{line}" if restrict for regexp in restrict if regexp.test(name) continue headers[name.toLowerCase()] = value.trim().replace(/^"(.*)"$/, "$1") return headers module.exports = Catalog