require 'sinatra/base' require 'json' # This is the fixture Rack application against which the integration # test will run. It uses class variables to persist data, because # Sinatra will instantiate a new instance of TestRackApp for each # request. class JsonApiApp < Sinatra::Base enable :sessions disable :show_exceptions def parse_json_req_body request.body.rewind JSON.parse(request.body.read, :symbolize_names => true) end post '/users' do user_data = parse_json_req_body @@users ||= {} @@users[user_data[:email]] = user_data status 201 headers 'Content-Type' => 'application/json' body user_data.to_json end post '/session' do user = @@users[params[:email]] if user && user[:password] == params[:password] session[:logged_in] = true status 200 body 'You are logged in!' else session[:logged_in] = false status 403 body 'Log in failed!' end end get '/session/new' do body <<-EOF <html> <head> <title>Sign In</title> </head> <body> <div id="sign_in_screen"> <form action="/session" method="POST"> <label for="email">Email:</label> <input id="email" name="email" type="text" /> <label for="password">Password:</label> <input id="password" name="password" type="password" /> <input type="submit" value="Sign In" /> </form> </div> </body> </html> EOF end def delete_widget(id) @@widgets.delete_if do |w| w[:id] == id end end post '/widgets/:widget_id' do delete_widget(params['widget_id']) redirect to('/widgets') end delete '/widgets/:widget_id' do result = delete_widget(params['widget_id']) status 200 body result.to_json end get '/widgets/new' do body <<-EOF <html> <head> <title>New Widget</title> </head> <body> <div id="widget_form"> <form action="/widgets" method="POST"> <label for="name">Name:</label> <input id="name" name="name" type="text" /> <input type="submit" value="Save" /> </form> </div> </body> </html> EOF end post '/widgets' do @@widgets ||= [] widget_data = if request.media_type == 'application/json' parse_json_req_body else {:name => params['name']} end widget_data[:id] = UUID.new.generate @@widgets << widget_data @@last_widget_created = widget_data request.accept.each do |type| case type.to_s when 'application/json' status 201 headers 'Content-Type' => 'application/json' halt widget_data.to_json else halt redirect to('/widgets') end end end get '/widgets' do @@widgets ||= [] if request.media_type == 'application/json' status 200 headers 'Content-Type' => 'application/json' halt @@widgets.to_json else raise "Not logged in!" unless session[:logged_in] last_widget_created, @@last_widget_created = @@last_widget_created, nil content = '' content << <<-EOF <html> <head> <title>Widgets</title> </head> <body> <div id="widget_list"> EOF if last_widget_created content << <<-EOF <div class="last_widget created"> <span class="id">#{last_widget_created[:id]}</span> <span class="name">#{last_widget_created[:name]}</span> </div> EOF end content << <<-EOF <ul> EOF @@widgets.each do |w| content << <<-EOF <li class="widget_summary"> <span class="id">#{w[:id]}</span> <span class="name">#{w[:name]}</span> <form id="delete_#{w[:id]}" action="/widgets/#{w[:id]}" method="POST"> <button type="submit" value="Delete" /> </form> </li> EOF end content << <<-EOF </ul> <a href="/widgets/new">New Widget</a> </div> </body> </html> EOF body content end end get '/error_page' do content = <<-EOF <html> <head> <title>Internal Server Error</title> </head> <body> <p>A Purposeful Error</p> </body> </html> EOF body content end error do e = request.env['sinatra.error'] body << <<-EOF <html> <head> <title>Internal Server Error</title> </head> <body> <pre> #{e.to_s}\n#{e.backtrace.join("\n")} </pre> </body> </html> EOF end end class MyAPIClient < Kookaburra::APIClient encode_with { |data| JSON.dump(data) } decode_with { |data| JSON.parse(data) } header 'Content-Type', 'application/json' header 'Accept', 'application/json' def create_user(user_data) post '/users', user_data end def create_widget(widget_data) post '/widgets', widget_data end def delete_widget(id) delete "/widgets/#{id}" end def widgets get '/widgets' end end class MyAPIDriver < Kookaburra::APIDriver def api MyAPIClient.new(configuration) end def create_user(name) mental_model.users.fetch(name) { |name| user = {'email' => 'bob@example.com', 'password' => '12345'} result = api.create_user(user) mental_model.users[name] = result } end def create_widget(name, attributes = {}) mental_model.widgets.fetch(name) { |name| widget = {'name' => name}.merge(attributes) result = api.create_widget(widget) mental_model.widgets[name] = result } end def delete_widget(name) widget = mental_model.widgets.delete(name) api.delete_widget(widget['id']) end def widgets api.widgets end end class SignInScreen < Kookaburra::UIDriver::UIComponent def component_path '/session/new' end # Use default component locator value # # def component_locator # '#sign_in_screen' # end def sign_in(user_data) fill_in 'Email:', :with => user_data['email'] fill_in 'Password:', :with => user_data['password'] click_button 'Sign In' end end class ErrorPage < Kookaburra::UIDriver::UIComponent def component_path '/error_page' end end class WidgetDataContainer def initialize(element) @element = element end def to_hash { 'id' => @element.find('.id').text, 'name' => @element.find('.name').text } end end class LastWidgetCreated < Kookaburra::UIDriver::UIComponent def component_locator @options[:component_locator] end def data raise "Foo" unless visible? WidgetDataContainer.new(self).to_hash end end class WidgetList < Kookaburra::UIDriver::UIComponent ui_component :last_widget_created, LastWidgetCreated, :component_locator => '#widget_list .last_widget.created' def component_path '/widgets' end def component_locator '#widget_list' end def widgets all('.widget_summary').map do |el| WidgetDataContainer.new(el).to_hash end end def choose_to_create_new_widget click_on 'New Widget' end def choose_to_delete_widget(widget_data) find("#delete_#{widget_data['id']}").click_button('Delete') end end class WidgetForm < Kookaburra::UIDriver::UIComponent def component_locator '#widget_form' end def submit(widget_data) fill_in 'Name:', :with => widget_data['name'] click_on 'Save' end end class MyUIDriver < Kookaburra::UIDriver ui_component :error_page, ErrorPage ui_component :sign_in_screen, SignInScreen ui_component :widget_list, WidgetList ui_component :widget_form, WidgetForm def sign_in(name) address_bar.go_to sign_in_screen sign_in_screen.sign_in(mental_model.users[name]) end def error_on_purpose address_bar.go_to error_page end def view_widget_list address_bar.go_to widget_list end def create_widget(name, attributes = {}) assert widget_list.visible?, "Widget list is not visible!" widget_list.choose_to_create_new_widget widget_form.submit('name' => 'My Widget') mental_model.widgets[name] = widget_list.last_widget_created.data end def delete_widget(name) assert widget_list.visible?, "Widget list is not visible!" widget_list.choose_to_delete_widget(mental_model.widgets.delete(name)) end end