All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.freeside.betamax.Recorder.groovy Maven / Gradle / Ivy

/*
 * Copyright 2011 Rob Fletcher
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package co.freeside.betamax

import java.util.logging.Logger
import co.freeside.betamax.proxy.jetty.ProxyServer
import co.freeside.betamax.tape.*
import co.freeside.betamax.tape.yaml.YamlTapeLoader
import co.freeside.betamax.util.*
import org.junit.rules.MethodRule
import org.junit.runners.model.*
import org.yaml.snakeyaml.introspector.PropertyUtils
import static TapeMode.READ_WRITE
import static co.freeside.betamax.MatchRule.*
import static java.util.Collections.EMPTY_MAP
/**
 * This is the main interface to the Betamax proxy. It allows control of Betamax configuration and inserting and
 * ejecting `Tape` instances. The class can also be used as a _JUnit @Rule_ allowing tests annotated with `@Betamax` to
 * run with the Betamax HTTP proxy in the background.
 */
class Recorder implements MethodRule {

	public static final String DEFAULT_TAPE_ROOT = 'src/test/resources/betamax/tapes'
	public static final int DEFAULT_PROXY_PORT = 5555
	public static final int DEFAULT_PROXY_TIMEOUT = 5000

	private static final log = Logger.getLogger(Recorder.name)

	Recorder() {
		def configFile = getClass().classLoader.getResource('BetamaxConfig.groovy')
		if (configFile) {
			def config = new ConfigSlurper().parse(configFile)
			configureFromConfig(config)
		} else {
			def propertiesFile = getClass().classLoader.getResource('betamax.properties')
			if (propertiesFile) {
				def properties = new Properties()
				propertiesFile.withReader { reader ->
					properties.load(reader)
				}
				configureFromProperties(properties)
			}
		}
	}

	Recorder(Properties properties) {
		configureFromProperties(properties)
	}

	/**
	 * The port the Betamax proxy will listen on.
	 */
	int proxyPort = DEFAULT_PROXY_PORT

	/**
	 * The base directory where tape files are stored.
	 */
	File tapeRoot = new File(DEFAULT_TAPE_ROOT)

	/**
	 * The default mode for an inserted tape.
	 */
	TapeMode defaultMode = READ_WRITE

	/**
	 * The time the proxy will wait before aborting a request in milliseconds.
	 */
	int proxyTimeout = DEFAULT_PROXY_TIMEOUT

	/**
	 * Hosts that are ignored by the proxy. Any connections made will be allowed to proceed normally and not be
	 * intercepted.
	 */
	Collection ignoreHosts = []

	/**
	 * If set to true all connections to localhost addresses are ignored.
	 * This is equivalent to setting `ignoreHosts` to `['localhost', '127.0.0.1', InetAddress.localHost.hostName,
	 * InetAddress.localHost.hostAddress]`.
	 */
	boolean ignoreLocalhost = false

	/**
	 * If set to true add support for proxying SSL (disable certificate checking)
	 */
	boolean sslSupport = false


	String getProxyHost() {
		proxy.url.toURI().host
	}

	int getHttpsProxyPort() {
		//proxyPort + 1
		proxyPort
	}

	private StorableTape tape
	private final HttpInterceptor proxy = new ProxyServer(this)

	/**
	 * Inserts a tape either creating a new one or loading an existing file from `tapeRoot`.
	 * @param name the name of the _tape_.
	 * @param arguments customize the behaviour of the tape.
	 */
	void insertTape(String name, Map arguments = [:]) {
		tape = tapeLoader.loadTape(name)
		tape.mode = arguments.mode ?: defaultMode
		tape.matchRules = arguments.match ?: [method, uri]
		tape
	}

	/**
	 * Gets the current active _tape_.
	 * @return the active _tape_.
	 */
	Tape getTape() {
		tape
	}

	/**
	 * 'Ejects' the current _tape_, writing its content to file. If the proxy is active after calling this method it
	 * will no longer record or play back any HTTP traffic until another tape is inserted.
	 */
	void ejectTape() {
		if (tape) {
			tapeLoader.writeTape(tape)
			tape = null
		}
	}

	/**
	 * Runs the supplied closure after starting the Betamax proxy and inserting a _tape_. After the closure completes
	 * the _tape_ is ejected and the proxy stopped.
	 * @param name the name of the _tape_.
	 * @param closure the closure to execute.
	 * @return the return value of the closure.
	 */
	def withTape(String name, Closure closure) {
		withTape(name, EMPTY_MAP, closure)
	}

	/**
	 * Runs the supplied closure after starting the Betamax proxy and inserting a _tape_. After the closure completes
	 * the _tape_ is ejected and the proxy stopped.
	 * @param tapeName the name of the _tape_.
	 * @param arguments arguments that affect the operation of the proxy.
	 * @param closure the closure to execute.
	 * @return the return value of the closure.
	 */
	def withTape(String tapeName, Map arguments, Closure closure) {
		try {
			startProxy(tapeName, arguments)
			closure()
		} finally {
			stopProxy()
		}
	}

	@Override
	Statement apply(Statement statement, FrameworkMethod method, Object target) {
		def annotation = method.getAnnotation(Betamax)
		if (annotation) {
			log.fine "found @Betamax annotation on '$method.name'"
			new Statement() {
				void evaluate() {
					withTape(annotation.tape(), [mode: annotation.mode(), match: annotation.match()]) {
						statement.evaluate()
					}
				}
			}
		} else {
			log.fine "no @Betamax annotation on '$method.name'"
			statement
		}
	}

	private void startProxy(String tapeName, Map arguments) {
		if (!proxy.running) {
			proxy.start()
		}
		insertTape(tapeName, arguments)
	}

	private void stopProxy() {
		proxy.stop()
		ejectTape()
	}

	private TapeLoader getTapeLoader() {
		new YamlTapeLoader(tapeRoot)
	}

	private void configureFromProperties(Properties properties) {
		use(PropertiesCategory) {
			tapeRoot = new File(properties.getProperty('betamax.tapeRoot', DEFAULT_TAPE_ROOT))
			proxyPort = properties.getInteger('betamax.proxyPort', DEFAULT_PROXY_PORT)
			proxyTimeout = properties.getInteger('betamax.proxyTimeout', DEFAULT_PROXY_TIMEOUT)
			defaultMode = properties.getEnum('betamax.defaultMode', READ_WRITE)
			ignoreHosts = properties.getProperty('betamax.ignoreHosts')?.tokenize(',') ?: []
			ignoreLocalhost = properties.getBoolean('betamax.ignoreLocalhost')
			sslSupport = properties.getBoolean('betamax.sslSupport')
		}
	}

	private void configureFromConfig(ConfigObject config) {
		tapeRoot = config.betamax.tapeRoot ?: DEFAULT_TAPE_ROOT
		proxyPort = config.betamax.proxyPort ?: DEFAULT_PROXY_PORT
		proxyTimeout = config.betamax.proxyTimeout ?: DEFAULT_PROXY_TIMEOUT
		defaultMode = config.betamax.defaultMode ?: READ_WRITE
		ignoreHosts = config.betamax.ignoreHosts ?: []
		ignoreLocalhost = config.betamax.ignoreLocalhost
		sslSupport = config.betamax.sslSupport
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy