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
+