diff --git a/Gemfile.lock b/Gemfile.lock index aa5e79bb..5dee68ef 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -37,11 +37,11 @@ GEM coffee-script-source execjs coffee-script-source (1.4.0) - daemon_controller (1.1.0) + daemon_controller (1.1.1) daemons (1.1.9) - delayed_job (3.0.4) + delayed_job (3.0.5) activesupport (~> 3.0) - delayed_job_active_record (0.3.3) + delayed_job_active_record (0.4.0) activerecord (>= 2.1.0, < 4) delayed_job (~> 3.0) diff-lcs (1.1.3) @@ -53,23 +53,23 @@ GEM fastthread (1.0.7) flexmock (0.9.0) hike (1.2.1) - hoe (3.4.0) + hoe (3.5.0) rake (>= 0.8, < 11.0) i18n (0.6.1) journey (1.0.4) - jquery-rails (2.1.4) + jquery-rails (2.2.1) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) json (1.7.6) libv8 (3.11.8.13) - libxml-ruby (2.4.0) + libxml-ruby (2.5.0) machinist (2.0) mail (2.4.4) i18n (>= 0.4.0) mime-types (~> 1.16) treetop (~> 1.4.8) memcache-client (1.8.5) - mime-types (1.19) + mime-types (1.21) multi_json (1.5.0) mysql2 (0.3.11) passenger (3.0.19) @@ -86,10 +86,10 @@ GEM prawn-format (0.2.0.1) prawn-core prawn-layout (0.2.0.1) - rack (1.4.3) + rack (1.4.5) rack-cache (1.2) rack (>= 0.4) - rack-ssl (1.3.2) + rack-ssl (1.3.3) rack rack-test (0.6.2) rack (>= 1.0) @@ -110,7 +110,7 @@ GEM thor (>= 0.14.6, < 2.0) rake (0.9.6) rb-readline (0.4.2) - rdoc (3.12) + rdoc (3.12.1) json (~> 1.4) ref (1.0.2) rspec (2.11.0) @@ -129,7 +129,7 @@ GEM ruby-ole (1.2.11.6) rubyzip (0.9.9) sass (3.2.5) - sass-rails (3.2.5) + sass-rails (3.2.6) railties (~> 3.2.0) sass (>= 3.1.10) tilt (~> 1.3) @@ -142,10 +142,10 @@ GEM tilt (~> 1.1, != 1.3.0) test-unit (1.2.3) hoe (>= 1.5.1) - therubyracer (0.11.1) - libv8 (~> 3.11.8.7) + therubyracer (0.11.3) + libv8 (~> 3.11.8.12) ref - thor (0.16.0) + thor (0.17.0) tilt (1.3.3) treetop (1.4.12) polyglot diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb new file mode 100644 index 00000000..dc5f8908 --- /dev/null +++ b/app/controllers/api_controller.rb @@ -0,0 +1,101 @@ +class ApiController < ApplicationController +skip_filter :set_current_user_and_project + before_filter :login_once + before_filter do |f| + f.require_permission(['ADMIN']) + end + respond_to :xml + + # example: Case.create_with_steps!({:created_by=>3,:updated_by=>3,:title => "Импорт", :date=>"2012-09-22T00:00:00",:priority=>"high", :time_estimate=>"", :objective=>"Цель",:test_data=>"Данные",:preconditions_and_assumptions=>"some prec",:test_area_ids=>[],:change_comment=>"Comment",:project_id=>1,:version=>1},[:version=>1,:position=>1,:action=>"step1",:result=>"res1"]) + def create_testcase + raise ApiError.new("Could not parse request as XML. Make sure to specify \'Content-type: text/xml\' when sending request", params.inspect) if params["request"].nil? or params["request"]["testcase"].nil? + attrs = params["request"]["testcase"] + steps = attrs["step"] + raise ApiError.new("Provided steps set is empty", params.inspect) if steps.nil? + steps.each{|s| + s["version"] = 1 + s["position"] = steps.index(s)+1 + } + project = Project.find_by_name(attrs["project"]) + raise ApiError.new("Project not found", attrs["project"]) if project.nil? + c = Case.create_with_steps!( + { # test attrs + :created_by => @current_user.id, + :updated_by => @current_user.id, + :title => attrs[:title], + :date => Date.today.to_s(:db), + :priority => attrs[:priority], + :objective => attrs[:objective], + :test_data => attrs[:data], + :preconditions_and_assumptions => attrs[:preconditions], + :project_id => project.id, + :version=>1 + }, + # steps + steps, + #tag_list + attrs[:tags] + ) + render :xml => { :result => "testcase #{c.title} created" }.to_xml + end + + def update_testcase_execution + attrs = params["request"] + raise ApiError.new("Could not parse request as XML. Make sure to specify \'Content-type: text/xml\' when sending request", params.inspect) if attrs.nil? + project = Project.find_by_name(attrs["project"]) + raise ApiError.new("Project not found", attrs["project"]) if project.nil? + # following assumptions are made: + # validates_uniqueness_of :name, :scope => :project_id (execution.rb) + # validates_uniqueness_of :title, :scope => :project_id (case.rb) + testcase_execution = CaseExecution.find_by_execution_id_and_case_id(project.executions.where(:name => attrs["execution"]).first, project.cases.where(:title => attrs["testcase"]).first) + raise ApiError.new("CaseExecution not found", "Test => #{attrs["testcase"]}, Execution => #{attrs["execution"]}") if testcase_execution.nil? + step_results = [] + attrs["step"].each{|se| + te = testcase_execution.step_executions.where(:position => se["position"].to_i) + raise ApiError.new("Case step with position #{se["position"].to_i} not found inside testcase #{attrs["testcase"]}", "Steps => #{testcase_execution.step_executions.collect(&:inspect)}") if te.empty? + step_result = {} + step_result["id"] = testcase_execution.step_executions.where(:position => se["position"].to_i).first.id + step_result["result"] = se["result"] + step_result["comment"] = se["comment"] + step_result["bug"] = nil + step_results << step_result + } + testcase_execution.update_with_steps!({"duration" => attrs["duration"]},step_results,@current_user) + render :xml => { :result => "execution #{attrs["execution"]} updated" }.to_xml + end + def block_testcase_execution + attrs = params["request"] + raise ApiError.new("Could not parse request as XML. Make sure to specify \'Content-type: text/xml\' when sending request", params.inspect) if attrs.nil? + testcase_execution = block_unblock(true,attrs) + render :xml => { :result => "execution #{attrs["execution"]} blocked" } + end + + def unblock_testcase_execution + attrs = params["request"] + raise ApiError.new("Could not parse request as XML. Make sure to specify \'Content-type: text/xml\' when sending request", params.inspect) if attrs.nil? + testcase_execution = block_unblock(false, attrs) + render :xml => { :result => "execution #{attrs["execution"]} unblocked" } + end + + private + def login_once + authenticate_or_request_with_http_basic do |username, password| + if can_do_stuff?(username,password) + set_current_user_and_project + end + end + end + + + def block_unblock(flag,attrs) + project = Project.find_by_name(attrs["project"]) + raise ApiError.new("Project not found", attrs["project"]) if project.nil? + # following assumptions are made: + # validates_uniqueness_of :name, :scope => :project_id (execution.rb) + # validates_uniqueness_of :title, :scope => :project_id (case.rb) + testcase_execution = CaseExecution.find_by_execution_id_and_case_id(project.executions.where(:name => attrs["execution"]).first, project.cases.where(:title => attrs["testcase"]).first) + raise ApiError.new("Case execution not found", "Test => #{attrs["testcase"]}, Execution => #{attrs["execution"]}") if testcase_execution.nil? + testcase_execution.update_attribute(:blocked, flag) + testcase_execution + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e24471ef..24bec675 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -6,7 +6,7 @@ class ApplicationController < ActionController::Base # protect_from_forgery - before_filter :set_current_user_and_project, :except => [:login] + before_filter :set_current_user_and_project, :except => [:login] before_filter :apply_currents before_filter :clean_data @@ -169,5 +169,21 @@ def set_current_user_and_project @current_user = User.find(request.env['REMOTE_USER'] || session[:user_id]) @project = @current_user.latest_project end - +protected + def can_do_stuff?(login,password) + if u = User.authenticate(login,password) + if !u.latest_project + flash.now[:notice] = "You have no project assignments." + return false + elsif u.deleted? + flash.now[:notice] = "You have been deleted." + return false + else + session[:user_id] = u.id + return true + end + end + flash.now[:notice] = "Login failed." + return false + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 70962acd..6de69f33 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -94,7 +94,6 @@ def permissions # POST /users def create @user = User.new(@data) - @user.new_random_password if @user.password.blank? @user.save! diff --git a/config/routes.rb b/config/routes.rb index 0d41de50..1bf88202 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -147,7 +147,16 @@ end end + resources :automation_tools + resources :backups, :only => [:new, :create] resources :csv_exports, :only => [:new, :create] resources :csv_imports, :only => [:new, :create] + match '/api/test', :controller => 'api', :action => 'test', :via => :get + match '/api/create_testcase', :controller => 'api', :action => 'create_testcase', :via => :post + match '/api/update_testcase_execution', :controller => 'api', :action => 'update_testcase_execution', :via => :post + match '/api/unblock_testcase_execution', :controller => 'api', :action => 'unblock_testcase_execution', :via => :post + match '/api/block_testcase_execution', :controller => 'api', :action => 'block_testcase_execution', :via => :post + match '/automation/execute', :controller => 'automation', :action => 'execute', :via => :get + match "/case_executions/automated/:id", :controller => 'case_executions', :action => 'automated', :via => :get end diff --git a/db/migrate/20130227181034_add_blocked_to_testcase_executions.rb b/db/migrate/20130227181034_add_blocked_to_testcase_executions.rb new file mode 100644 index 00000000..03ef9c4c --- /dev/null +++ b/db/migrate/20130227181034_add_blocked_to_testcase_executions.rb @@ -0,0 +1,5 @@ +class AddBlockedToTestcaseExecutions < ActiveRecord::Migration + def change + add_column :case_executions, :blocked, :boolean, :default => 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index 2ac19644..a6782799 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20120905062740) do +ActiveRecord::Schema.define(:version => 20130227181034) do create_table "attachment_sets", :force => true do |t| t.datetime "created_at" @@ -182,6 +182,7 @@ t.integer "duration", :default => 0 t.integer "position", :default => 0 t.string "title" + t.boolean "blocked", :default => false end add_index "case_executions", ["case_id"], :name => "index_case_executions_on_case_id" diff --git a/lib/api_error.rb b/lib/api_error.rb new file mode 100644 index 00000000..d9dc6992 --- /dev/null +++ b/lib/api_error.rb @@ -0,0 +1,5 @@ +class ApiError < StandardError + def initialize(msg, request_details) + super msg.upcase + "\tREQUETS DETAILS:\t#{request_details}\n" + end +end diff --git a/lib/authenticator.rb b/lib/authenticator.rb index 4c09d4ae..ac5c5333 100644 --- a/lib/authenticator.rb +++ b/lib/authenticator.rb @@ -5,7 +5,7 @@ =end class Authenticator - UnAuthenticated = /^(\/home\/login|\/home\/logout|\/password_resets.*|\/assets.*)$/ + UnAuthenticated = /^(\/home\/login|\/home\/logout|\/password_resets.*|\/assets.*|\/api.*)$/ ###### class SessionAuth < Rack::Auth::AbstractHandler diff --git a/spec/controllers/api_controller_spec.rb b/spec/controllers/api_controller_spec.rb new file mode 100644 index 00000000..e3216117 --- /dev/null +++ b/spec/controllers/api_controller_spec.rb @@ -0,0 +1,279 @@ +require File.expand_path(File.dirname(__FILE__) + '/../controller_spec_helper') +require 'faker' +require 'builder' + +def encode_credentials(u,p) + ActionController::HttpAuthentication::Basic.encode_credentials(u,p) +end + +def create_testcase_body( + project=@project.name, + title=@testcase.title, + priority='high', + tags='tag1,tag2', + objective='test objective', + test_data='test data', + preconditions='test preconditions', + steps=[{:action => 'a1', :result => 'r1'}, {:action => 'a2', :result => 'a2'}] +) +# +# +# +# +# +# +{"request" => + {"testcase"=> + { + "project" => project, + "title"=> title, + "priority"=> priority, + "tags"=> tags, + "objective"=> objective, + "data"=> test_data, + "preconditions"=> preconditions, + "step"=> steps + } + } +} +end + +def update_testcase_execution_body( + project=@project.name, + execution=@execution.name, + testcase=@testcase.title, + duration='1', + steps=[{:position => 1, :result => 'PASSED'}] +) +# +# My project +# My execution +# My testcase +# 1 +# +# +{"request" => + { + "project" => project, + "execution" => execution, + "testcase" => testcase, + "duration" => duration, + "step" => steps + } +} +end + +def block_or_unblock_testcase_body( + project=@project.name, + execution=@execution.name, + testcase=@testcase.title +) +# +# My project +# My execution +# My testcase +# + {"request" => + { + "project" => project, + "execution" => execution, + "testcase" => testcase, + } + } +end + +alias :unblock_testcase_execution_body :block_or_unblock_testcase_body +alias :unblock_testcase_execution_body :block_or_unblock_testcase_body +alias :block_testcase_execution_body :block_or_unblock_testcase_body + +shared_examples_for "api_method" do |method_name| + + context "unauthorized user" do + it "returns Access denied" do + request.env['HTTP_AUTHORIZATION'] = encode_credentials('unauthorized user', 'password') + post method_name + response.body.strip.should eq "HTTP Basic: Access denied." + end + end + + context "invalid XML provided" do + it "returns \"Invalid XML provided\" 500 error" do + request.env['RAW_POST_DATA'] = { :request => {} }.to_xml + post method_name + response.body.should =~ /COULD NOT PARSE REQUEST AS XML/ + end + end + + context "provided project not found" do + it "returns \"Project not found\" 500 error" do + post method_name, eval(method_name.to_s + "_body('whible')") + response.body.should =~ /PROJECT NOT FOUND/ + end + end +end + +describe ApiController do + before :all do + tc_title = Faker::Name.name + ex_title = Faker::Name.name + @user = User.create!( + :login => Faker::Internet.user_name, + :realname => Faker::Name.name, + :email => Faker::Internet.email, + :phone => '', + :admin => true, + :password => 'password', + :password_confirmation => 'password' + ) + @project = Project.first + @user.project_assignments.create!(:project => @project, :group => 'TEST_DESIGNER') + @to = TestObject.find_by_name('test_object_name') || TestObject.create!(:name => 'test_object_name', :project_id => @project.id, :date => "2013-02-26T00:00:00") + @testcase = Case.create_with_dummy_step(:title => tc_title+' dummy', :created_by => @user.id, :updated_by => @user.id, + :project_id => @project.id, :date => "2013-02-26T00:00:00") + @testcase_2steps = Case.create_with_steps!({:title => tc_title+' 2 steps', :created_by => @user.id, :updated_by => @user.id, + :project_id => @project.id, :date => "2013-02-26T00:00:00"}, [{:action => 'a1', :result => 'r1', :position => 1},{:action => 'a2', :result => 'r2', :position => 2}]) + @execution = Execution.create_with_assignments!({:name => ex_title, :test_object_id => @to.id, + :project_id => @project.id, :date => "2013-02-26T00:00:00"}, [@testcase, @testcase_2steps], @user.id) + end + + before(:each) do + request.env['HTTP_AUTHORIZATION'] = encode_credentials(@user.login, @user.password) + end + + describe "#create_testcase" do +=begin + + + + + + +=end + it_should_behave_like "api_method", :create_testcase + + + context "incorrect parameters" do + it "raps to blank test title" do + post 'create_testcase', create_testcase_body(@project.name, '') + response.body.should eq "Validation failed: Title can't be blank" + end + + it "raps to invalid priority" do + post 'create_testcase', create_testcase_body(@project.name, @testcase.title, 'not existing priority') + response.body.should =~ /Invalid priority 'not existing priority'/ + end + + it "raps to empty steps set" do + post 'create_testcase', create_testcase_body(@project.name, @testcase.title, 'high',nil,nil,nil,nil,nil) + response.body.should =~ /PROVIDED STEPS SET IS EMPTY/ + end + end + + context "correct parameters" do + it "creates test with 0 steps" do + title = Faker::Name.name + post 'create_testcase', create_testcase_body(@project.name, title, 'high',nil,nil,nil,nil,[]) + response.body.should =~ /testcase #{title} created/ + end + + it "creates test with 5 steps" do + title = Faker::Name.name + def step(i); { "action" => "a#{i}", "result" => "r#{i}" }; end + steps = [] + 5.times{ |i| steps << step(i) } + post 'create_testcase', create_testcase_body(@project.name, title, 'high',nil,nil,nil,nil,steps) + + response.body.should =~ /testcase #{title} created/ + end + end + end + + describe "#update_testcase_execution" do +=begin + + calculator + CALC + 2+2=4 + 1 + + + +=end + it_should_behave_like "api_method", :update_testcase_execution + + context "incorrect parameters" do + # execution=@execution.name, testcase=@testcase.title, duration='1', steps=[{:position => '1', :result => 'PASSED'}, {:position => '2', :result => 'FAILED'}] + it "raps to invalid execution title" do + post 'update_testcase_execution', update_testcase_execution_body(@project.name, 'unknown_execution') + response.body.should =~ /CASEEXECUTION NOT FOUND/ + end + + it "raps to invalid case title" do + post 'update_testcase_execution', update_testcase_execution_body(@project.name, @execution.name, 'unknown_testcase') + response.body.should =~ /CASEEXECUTION NOT FOUND/ + end + + it "raps to invalid steps array" do + post 'update_testcase_execution', update_testcase_execution_body(@project.name, @execution.name, @testcase.title, '1', [1,23,4]) + response.body.should =~ /CASE STEP WITH POSITION \d+ NOT FOUND/ + end + + it "raps to invalid result" do + post 'update_testcase_execution', update_testcase_execution_body(@project.name, @execution.name, @testcase.title, '1', + [{:position => '1', :result => 'WHIBLE'}]) + response.body.should =~ /Invalid result type WHIBLE!/ + end + + end + + context "correct parameters" do + it "updates test with 1 step" do + post 'update_testcase_execution', update_testcase_execution_body + response.body.should =~ /execution #{@execution.name} updated/ + end + + it "updates test with 2 steps" do + post 'update_testcase_execution', update_testcase_execution_body(@project.name, @execution.name, @testcase_2steps.title, '1', + [{:position => '1', :result => 'PASSED'}, {:position => '2', :result => 'FAILED'}]) + response.body.should =~ /execution #{@execution.name} updated/ + end + end + end + + describe "#(un)block_testcase_execution" do +=begin + + My project + My execution + My testcase + +=end + it_should_behave_like "api_method", :block_testcase_execution + it_should_behave_like "api_method", :unblock_testcase_execution + + context "incorrect parameters" do + it "raps to invalid execution name or testcase title" do + post 'block_testcase_execution', block_or_unblock_testcase_body(@project.name, 'unknown_execution') + response.body.should =~ /CASE EXECUTION NOT FOUND/ + post 'block_testcase_execution', block_or_unblock_testcase_body(@project.name, @execution.name, 'unknown testcase') + response.body.should =~ /CASE EXECUTION NOT FOUND/ + post 'unblock_testcase_execution', block_or_unblock_testcase_body(@project.name, 'unknown_execution') + response.body.should =~ /CASE EXECUTION NOT FOUND/ + post 'unblock_testcase_execution', block_or_unblock_testcase_body(@project.name, @execution.name, 'unknown testcase') + response.body.should =~ /CASE EXECUTION NOT FOUND/ + end + end + + context "correct parameters" do + it "updates case execution :blocked parameter to flag" do + flag = 1 + post 'block_testcase_execution', block_or_unblock_testcase_body + response.body.should =~ /execution #{@execution.name} blocked/ + flag = 0 + post 'unblock_testcase_execution', block_or_unblock_testcase_body + response.body.should =~ /execution #{@execution.name} unblocked/ + end + end + end +end +