Permalink
Browse files

Initial commit

  • Loading branch information...
0 parents commit 35fd0216cdce7fceba22ddf198f7b66d4afa76c7 @ayanko ayanko committed Jan 7, 2012
@@ -0,0 +1,4 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
10 Gemfile
@@ -0,0 +1,10 @@
+source "http://rubygems.org"
+
+gemspec
+
+gem 'rspec'
+gem 'capybara'
+
+gem 'rack'
+gem 'sinatra'
+gem 'rails'
105 README.md
@@ -0,0 +1,105 @@
+# rack_session_access
+
+RackSessionAccess provides rack middleware for 'rack.session' environment management.
+
+## Problem
+
+Acceptance testing assumes that you can't directly access application session.
+For example if you use capybara with selenium webdriver you can't change some session value
+because your test use browser that access application via backend server.
+
+## Solution
+
+But if you still want to change session values?
+Possible solution is inject into application some code that will manage session.
+If you use rack based framework this gem does it!
+
+## Installation
+
+ gem install rack_session_access
+
+## Using with Rails3
+
+Add to `Gemfile`:
+
+ gem 'rack_session_access'
+
+Add to `config/application.rb`
+
+ module MyRailsApplication
+ class Application < Rails::Application
+ config.middleware.use RackSessionAccess::Middleware if Rails.env.test?
+ end
+ end
+
+*Note* Ensure you include rack_session_access middleware only for test environment
+otherwise you will have security issue.
+
+If you use rspec you may prefer to inject middleware only for rspec tests:
+Put into `spec/spec_helper`:
+
+ Rails.application.configure do
+ config.middleware.use RackSessionAccess::Middleware
+ end
+
+## Using with Sinatra
+
+Add to your sinatra application:
+
+ class MySinatraApplication < Sinatra::Base
+ enable :sessions
+ use RackSessionAccess if environment == :test
+ ...
+ end
+
+If you use rspec you may prefer to inject middleware only for rspec tests:
+Put into `spec/spec_helper`:
+
+ MySinatraApplication.configure do
+ use RackSessionAccess::Middleware
+ end
+
+## Using with Rack builder
+
+ Rack::Builder.new do
+ ...
+ use Rack::Session::Cookie
+ use RackSessionAccess::Middleware
+ use MyRackApplication
+ end.to_app
+
+## Testing with Capybara
+
+Add to `spec/spec_helper.rb`
+
+ require "rack_session_access/capybara"
+
+And use `page.set_rack_session` to set your desired session data!
+
+Example:
+
+ require 'spec_helper'
+
+ feature "My feature" do
+ background do
+ @user = Factory(:user, :email => 'jack@daniels.com')
+ end
+
+ scenario "logged in user access profile page" do
+ page.set_rack_session(:user_id => user.id)
+ page.visit "/profile"
+ page.should have_content("Hi, jack@daniels.com")
+ end
+ end
+
+
+## Notes
+
+Thus we use marshalized data it's possible to set any ruby object into application session hash.
+
+Enjoy!
+
+
+## References
+
+* [capybara](https://github.com/jnicklas/capybara)
@@ -0,0 +1 @@
+require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
+require 'rack'
+
+TestRackApp = Rack::Builder.new do
+ use Rack::Session::Cookie
+ use RackSessionAccess::Middleware
+ run lambda { |env| [200, { 'Content-Type' => 'text/plain'}, [ "DUMMY"] ] }
+end.to_app
@@ -0,0 +1,35 @@
+require 'rails'
+require 'action_controller/railtie'
+
+module TestRailsApp
+ class Application < Rails::Application
+ config.secret_token = '572c86f5ede338bd8aba8dae0fd3a326aabababc98d1e6ce34b9f5'
+
+ routes.draw do
+ get '/login' => 'test_rails_app/sessions#new'
+ post '/login' => 'test_rails_app/sessions#create'
+ get '/profile' => 'test_rails_app/profiles#show'
+ end
+ end
+
+ class SessionsController < ActionController::Base
+ def new
+ render :text => "Please log in"
+ end
+
+ def create
+ session[:user_email] = params[:user_email]
+ redirect_to '/profile'
+ end
+ end
+
+ class ProfilesController < ActionController::Base
+ def show
+ if user_email = session[:user_email]
+ render :text => "Welcome, #{user_email}!"
+ else
+ redirect_to '/login'
+ end
+ end
+ end
+end
@@ -0,0 +1,22 @@
+require 'sinatra'
+
+class TestSinatraApp < Sinatra::Base
+ enable :sessions
+
+ get '/login' do
+ body "Please log in"
+ end
+
+ post '/login' do
+ session[:user_email] = params[:user_email]
+ redirect to('/profile')
+ end
+
+ get '/profile' do
+ if user_email = session[:user_email]
+ body "Welcome, #{user_email}!"
+ else
+ redirect to('/login')
+ end
+ end
+end
@@ -0,0 +1,30 @@
+module RackSessionAccess
+ autoload :Middleware, 'rack_session_access/middleware'
+
+ class << self
+ # session resource path
+ attr_accessor :path
+
+ # session resource edit path
+ attr_accessor :edit_path
+
+ # encode session hash to string
+ def encode(hash)
+ Array(Marshal.dump(hash)).pack('m')
+ end
+
+ # decode string to session hash
+ def decode(string)
+ Marshal.load(string.unpack('m').first)
+ end
+
+ def configure
+ yield self
+ end
+ end
+end
+
+RackSessionAccess.configure do |config|
+ config.path = '/rack_session'
+ config.edit_path = '/rack_session/edit'
+end
@@ -0,0 +1,16 @@
+module RackSessionAccess
+ module Capybara
+ def set_rack_session(hash)
+ data = ::RackSessionAccess.encode(hash)
+
+ visit ::RackSessionAccess.edit_path
+ has_content?("Update rack session")
+ fill_in "data", :with => data
+ click_button "Update"
+ has_content?("Rack session data")
+ end
+ end
+end
+
+require 'capybara/session'
+Capybara::Session.send :include, RackSessionAccess::Capybara
@@ -0,0 +1,136 @@
+require 'rack/request'
+require 'builder'
+
+module RackSessionAccess
+ class Middleware
+ # Initialize RackSessionAccess middleware
+ #
+ # @param app a rack application
+ # @param options
+ #
+ # Options:
+ # * :key - rack session key
+ def initialize(app, options = {})
+ @app = app
+ @key = options[:key] || 'rack.session'
+ @routing = [
+ [ 'GET', RackSessionAccess.path, :show ],
+ [ 'GET', RackSessionAccess.edit_path, :edit ],
+ [ 'PUT', RackSessionAccess.path, :update ]
+ ]
+ end
+
+ def call(env)
+ return render(500) do |xml|
+ xml.h2("#{@key} env is not initialized")
+ end unless env[@key]
+
+ request = ::Rack::Request.new(env)
+
+ if action = dispatch_action(request)
+ send(action, request)
+ else
+ @app.call(env)
+ end
+ end
+
+ protected
+
+ # List session data
+ def show(request)
+ # call inspect because session can be lazy loaded
+ request.env[@key].inspect
+
+ render do |xml|
+ xml.h2 "Rack session data"
+ xml.ul do |xml|
+ request.env[@key].each do |k,v|
+ xml.li("#{k.inspect} : #{v.inspect}")
+ end
+ end
+ xml.p do |xml|
+ xml.a("Edit", :href => action_path(:edit))
+ end
+ end
+ end
+
+ # Render form for submit new session data
+ def edit(request)
+ render do |xml|
+ xml.h2 "Update rack session"
+ xml.p "Put marshalized and encoded with base64 ruby hash into the form"
+ xml.form({
+ :action => action_path(:update),
+ :method => 'post',
+ :enctype => 'application/x-www-form-urlencoded'
+ }) do |xml|
+ xml.input(:type => 'hidden', :name =>'_method', :value => 'put')
+ xml.textarea("", :cols => 40, :rows => 10, :name => 'data')
+ xml.p do |xml|
+ xml.input(:type => 'submit', :value => "Update")
+ end
+ end
+ end
+ end
+
+ # Update session data
+ def update(request)
+ begin
+ data = request.params['data']
+ hash = RackSessionAccess.decode(data)
+ hash.each { |k, v| request.env[@key][k] = v }
+ rescue => e
+ return render(400) do |xml|
+ xml.h2("Bad data #{data.inspect}: #{e.message} ")
+ end
+ end
+
+ redirect_to action_path(:show)
+ end
+
+ private
+
+ # Dispatch action from request
+ def dispatch_action(request)
+ method = request_method(request)
+ path = request.path
+ route = @routing.detect { |r| r[0] == method && r[1] == path }
+ route[2] if route
+ end
+
+ # Return HTTP method, detect emulated method with _method param
+ def request_method(request)
+ return request.request_method if request.request_method != 'POST'
+ return request.params['_method'].upcase if request.params['_method']
+ request.request_method
+ end
+
+ # @return path for given action name
+ def action_path(action)
+ @routing.detect { |r| r[2] == action }[1]
+ end
+
+ # @return redirect response to specified url
+ def redirect_to(url)
+ render(302, {"Location" => url}) do |xml|
+ xml.a "You are being redirected", :href => url
+ end
+ end
+
+ # @return html response
+ def render(code = 200, headers = {})
+ headers["Content-Type"] ||= "text/html"
+
+ builder = Builder::XmlMarkup.new(:indent => 2)
+
+ builder.html do |xml|
+ xml.body do |xml|
+ yield xml
+ end
+ end
+
+ [ code, headers, [builder.target!] ]
+ end
+
+ end
+end
@@ -0,0 +1,3 @@
+module RackSessionAccess
+ VERSION = "0.0.1"
+end
Oops, something went wrong.

0 comments on commit 35fd021

Please sign in to comment.