#!/usr/bin/env ruby 
require 'xmlrpc/client'
# Author:: Sadahiko Hantani (garyohosu@gmail.com)
# Copyright:: Copyright (c) 2008 Sadahiko Hantani
# License::   Distributes under GPL
# 
# 改定履歴
#- 0.01 2008/12/14 新規作成
#- 0.02 2008/12/15 先頭にバイナリが入っていたので修正
#- 0.03 2008/12/15 コメントを追加
#- 0.04 2008/12/16 getProjectID,getTestPlanID,getBuildIDで最もビルドIDの大きなIDを返すよう変更.getStatusByTCNameを追加
# 


class TestLinkClient
#
#=== 概要
#
# initialize
#
#=== 引数
#
#+server_url+::
# TestLink Server URL
#+dev_key+::
# Personal API access key 
#+api_path+::
# xmlrpc.php path (Option) 
#
#=== 戻り値
#
# なし
#
#=== 例外
#
#=== 詳細
#
#  TestLinkを設置したサーバーのURLとPersonal API access keyを設定します。
#  Personal API access keyを入手するには以下を実行してください。
#  
#1. TestLinkのconfig.inc.phpを変更します
#    /** SOAP API availability (disabled by default) */
#    $tlCfg->api_enabled = TRUE;
#2. user_api_keyを取得
#    TestLinkを開き「Personal」を開く。
#    「Generate a new key」ボタンをクリック
#    Personal API access key = xxxxxxxxxxxxxxxxxxxxxxxxxxxx
#
#=== 例
#  client = TestLinkClient.new("http://foo.org/testlink_18RC1","413a5f43341axxxxxx91f166501740ec")
#
  def initialize(server_url,dev_key,api_path = "/lib/api/xmlrpc.php")
    @server = XMLRPC::Client.new2(server_url + api_path)
    @devKey = dev_key
  end


#
#=== 概要
#
# reportTCResult(tpid,tcid,status,bid,notes,guess)
#
#=== 引数
#
#+tpid+::
# テスト計画ID
#+tcid+::
# テストケースID
#+status+::
# テスト結果  p:成功 f:失敗 b:ブロック
#+bid+::
# ビルドID(オプション)
#+notes+::
# ノート(オプション)
#+guess+::
# (オプション)
#
#=== 戻り値
# 例
# [{"message"=>"Success!", "status"=>true, "id"=>"2"}]
#
#=== 例外
#
#=== 詳細
#
# テスト結果を登録する
#
#  message:メッセージ
#  id:ID
#  status:true 成功 false 失敗
#
#=== 例
#
# reportTCResult(tpid,bid,tcid,status) #=>[{"message"=>"Success!", "status"=>true, "id"=>"2"}]
#
  def reportTCResult(tpid,tcid,status,bid=nil,notes=nil,guess=nil)

    args = {"devKey"=>@devKey,"testcaseid"=>tcid.to_i,"testplanid"=>tpid,"status"=>status,"buildid"=>bid}

    if bid then
      args["buildid"] = bid
    end
    if notes then #string
      args["notes"] = notes
    end
    if guess then #bool
      args["guess"] = guess
    end
    ret = @server.call("tl.reportTCResult",args)
  end

#
#=== 概要
#
# getProjects
#
#=== 引数
#
# なし
#
#=== 戻り値
#
# 現在のテストプロジェクト情報が返ります
# 例
#[{"name"=>"project", "prefix"=>"test", "tc_counter"=>"15", "option_automation"=>"1", "option_priority"=>"1", "notes"=>"", "id"=>"1", "color"=>"", "option_reqs"=>"1", "active"=>"0"}, {"name"=>"testproject2", "prefix"=>"tp2", "tc_counter"=>"6", "option_automation"=>"1", "option_priority"=>"1", "notes"=>"<p>comment</p>","id"=>"35", "color"=>"", "option_reqs"=>"1", "active"=>"1"}]
#
#=== 詳細
#
# プロジェクト毎に以下の値を得ることができます
#
# name:テストプロジェクト名
# prefix:プレフィックス
# tc_counter:テストケース数
# option_automation:automationを使用するか
# option_priority:優先度を使用するか
# notes:ノート
# id:テストプロジェクトID
# color:
# option_reqs:要件管理を使用するか
# active:アクティブプロジェクトか
#
#=== 例
# getProjects #=>[{"name"=>"project", "prefix"=>"test", "tc_counter"=>"15", "option_automation"=>"1", "option_priority"=>"1", "notes"=>"", "id"=>"1", "color"=>"", "option_reqs"=>"1", "active"=>"0"}]
#
  def getProjects
    args = {"devKey"=>@devKey}
    ret = @server.call("tl.getProjects",args)
  end

#
#=== 概要
#
# getProjectTestPlans(pid)
#
#=== 引数
#
#+pid+::
# テストプロジェクトID
#
#=== 戻り値
# 例
# [{"3"=>{"name"=>"test plan", "notes"=>"note", "id"=>"3", "testproject_id"=>"1", "active"=>"1"}}]
#
#=== 例外
#
#=== 詳細
#
# テストプロジェクトIDを渡すとテスト計画の詳細情報を返す
#
#  -   :テスト計画ID
#  name:テスト計画名
#  notes:ノート
#  id:自ID
#  testproject_id:テストプロジェクトID
#  active:アクティブか
#
#=== 例
#
# getProjectTestPlans(pid) #=>[{"3"=>{"name"=>"test plan", "notes"=>"note", "id"=>"3", "testproject_id"=>"1", "active"=>"1"}}]
#
  def getProjectTestPlans(pid)
    args = {"devKey"=>@devKey,"testprojectid"=>pid}     
    ret = @server.call("tl.getProjectTestPlans",args)
  end


#
#=== 概要
#
# createBuild(testplanid,buildname,buildnotes)
#
#=== 引数
#
#+testplanid+::
# テスト計画ID
#+buildname+::
# ビルド名
#+buildnotes+::
# ビルドノート
#
#=== 戻り値
# 例
# [{"message"=>"Success!", "status"=>true, "id"=>"4"}]
#
#=== 例外
#
#=== 詳細
#
# テストプロジェクトID,ビルド名、ビルドノートを渡すと新規ビルドを生成する
#  -   :ビルドID
#  name:ビルド名
#  notes:ノート
#  id:ビルドID
#  testproject_id:テストプロジェクトID
#  active:アクティブか？
#
#=== 例
#
# createBuild(testplanid,buildname,buildnotes) #=>[{"message"=>"Success!", "status"=>true, "id"=>"2"}]
#
  def createBuild(testplanid,buildname,buildnotes)
    args = {"devKey"=>@devKey,"testplanid"=>testplanid,"buildname"=>buildname,"buildnotes"=>buildnotes}     
    ret = @server.call("tl.createBuild",args)
  end

#
#=== 概要
#
# getBuildsForTestPlan(tpid)
#
#=== 引数
#
#+tpid+::
# テスト計画ID
#
#=== 戻り値
# 例
# [{"name"=>"build1", "notes"=>"", "id"=>"1", "is_open"=>"1", "testplan_id"=>"3", "active"=>"1"}]
#
#=== 例外
#
#=== 詳細
#
# テスト計画IDを渡すとビルドの詳細情報を返す
#  name:ビルド名
#  notes:ノート
#  id:自ID
#  is_open:オープンか？
#  testplan_id:テスト計画ID
#  active:アクティブか
#
#=== 例
#
# getBuildsForTestPlan(tpid) #=>[{"name"=>"build1", "notes"=>"", "id"=>"1", "is_open"=>"1", "testplan_id"=>"3", "active"=>"1"}]
#
  def getBuildsForTestPlan(tpid)
    args = {"devKey"=>@devKey,"testplanid"=>tpid}     
    ret = @server.call("tl.getBuildsForTestPlan",args)
  end  

#
#=== 概要
#
# getTestSuitesForTestPlan(tpid)
#
#=== 引数
#
#+tpid+::
# テスト計画ID
#
#=== 戻り値
# 例
# {"name"=>"testsuite1", "id"=>"36"}
#
#=== 例外
#
#=== 詳細
#
# テスト計画IDを渡すとテストスイートの詳細情報を返す
#  name:テストスイート名
#  id:自ID
#
#=== 例
#
# getTestSuitesForTestPlan(tpid) #=>{"name"=>"testsuite1", "id"=>"36"}
#
  def getTestSuitesForTestPlan(tpid)
    args = {"devKey"=>@devKey,"testplanid"=>tpid}     
    ret = @server.call("tl.getTestSuitesForTestPlan",args)
  end  
#
#=== 概要
#
# getTestCasesForTestSuite(tsid)
#
#=== 引数
#
#+tsid+::
# テストスイートID
#+deep+::
# 再帰的検索を行う(オプション) デフォルトはTrue
#
#=== 戻り値
# 例
# {"name"=>"testsuite1", "id"=>"36"}
#
#=== 例外
#
#=== 詳細
#
# テストスイートIDを渡すとテストケースの詳細情報を返す
#  name:テストケース名
#  id:テストケースID
#  node_type_id:
#  parent_id:
#  node_order:
#
#=== 例
#
# getTestCasesForTestSuite(tsid) #=>{"name"=>"testsuite1", "id"=>"36"}
#
  def getTestCasesForTestSuite(ts,deep=nil)
    args = {"devKey"=>@devKey,"testsuiteid"=>ts}

    if deep then #
      args["deep"] = deep
    end

    ret = @server.call("tl.getTestCasesForTestSuite",args)
  end  

#
#=== 概要
#
# getTestCasesForTestPlan(tpid,tcid,bldid,keyid,execid,assignedto,executestatus)
#
#=== 引数
#
#+tpid+::テスト計画ID
#+tcid+::テストケースID（オプション）
#+bldid+::ビルドID（オプション）
#+keyid+::キーID（オプション）
#+executed+::（オプション）
#+assignedto+::（オプション）
#+executestatus+::（オプション）
#
#=== 戻り値
# 例
#{"45"=>{"exec_status"=>"n", "status"=>"", "name"=>"testcase1", "assigner_id"=>"", "urgency"=>"2", "exec_id"=>"", "type"=>"", "exec_on_tplan"=>"", "executed"=>"", "external_id"=>"4", "z"=>"100", "tc_id"=>"45", "version"=>"1", "tcversion_id"=>"46", "execution_order"=>"40", "user_id"=>"", "feature_id"=>"19", "tcversion_number"=>"", "testsuite_id"=>"44", "active"=>"1"}, "41"=>{"exec_status"=>"p", "status"=>"", "name"=>"testcase3", "assigner_id"=>"", "urgency"=>"2", "exec_id"=>"42", "type"=>"", "exec_on_tplan"=>"43", "executed"=>"42", "external_id"=>"3", "z"=>"100", "tc_id"=>"41", "version"=>"1", "tcversion_id"=>"42", "execution_order"=>"30", "user_id"=>"", "feature_id"=>"18", "tcversion_number"=>"1", "testsuite_id"=>"36", "active"=>"1"}}
#=== 例外
#
#=== 詳細
#
# テスト計画IDを渡すとテストケースの詳細情報を返す
#  -   :テストケースID
#  exec_status:実行結果 p or f or b
#  status:
#  name:テストケース名
#  assigner_id:
#  urgency:
#  exec_id:
#  type:
#  exec_on_tplan:
#  executed:
#  external_id:
#  z:
#  tc_id:テストケースID
#  version:テストケースバージョン
#  tcversion_id:テストケースバージョンID
#  execution_order:
#  user_id:
#  feature_id:
#  tcversion_number:
#  testsuite_id:テストスイートID
#  active:アクティブか？
#
#=== 例
#
# getTestCasesForTestPlan(tpid) #=>{"45"=>{"exec_status"=>"n", "status"=>"", "name"=>"testcase1", "assigner_id"=>"", "urgency"=>"2", "exec_id"=>"", "type"=>"", "exec_on_tplan"=>"", "executed"=>"", "external_id"=>"4", "z"=>"100", "tc_id"=>"45", "version"=>"1", "tcversion_id"=>"46", "execution_order"=>"40", "user_id"=>"", "feature_id"=>"19", "tcversion_number"=>"", "testsuite_id"=>"44", "active"=>"1"}, "41"=>{"exec_status"=>"p", "status"=>"", "name"=>"testcase3", "assigner_id"=>"", "urgency"=>"2", "exec_id"=>"42", "type"=>"", "exec_on_tplan"=>"43", "executed"=>"42", "external_id"=>"3", "z"=>"100", "tc_id"=>"41", "version"=>"1", "tcversion_id"=>"42", "execution_order"=>"30", "user_id"=>"", "feature_id"=>"18", "tcversion_number"=>"1", "testsuite_id"=>"36", "active"=>"1"}}
#
  def getTestCasesForTestPlan(tpid,tcid=nil,bldid=nil,keyid=nil,executed=nil,assignedto=nil,executestatus=nil)
    args = {"devKey"=>@devKey,"testplanid"=>tpid}
    if tcid then
      args["testcaseid"] = tcid
    end
    if bldid then
      args["buildid"] = bldid
    end
    if keyid then
      args["keywordid"] = keyid
    end
    if executed then #boolean
      args["executed"] = executed
    end
    if assignedto then
      args["assignedto"] = assignedto
    end
    if executestatus then #string
      args["executestatus"] = executestatus
    end
    ret = @server.call("tl.getTestCasesForTestPlan",args)
  end  


#
#=== 概要
#
# getTestCaseIDByName(testcasename,testsuitename)
#
#=== 引数
#
#+testcasename+::
# テストケース名
#+testsuitename+::
# テストスイート名(オプション)
#
#=== 戻り値
# 例
# {"name"=>"testsuite1", "id"=>"36"}
#
#=== 例外
#
#=== 詳細
#
# テストケース名、テストスイート名を渡すとテストケースIDを返す
#  name:テストケース名
#  id:テストケースID
#
#=== 例
#
# getTestCaseIDByName(tsid) #=>[{"name"=>"testcase1", "id"=>"37"}]
#
  def getTestCaseIDByName(testcasename,testsuitename = nil)
    args = {"devKey"=>@devKey,"testcasename"=>testcasename}

    if testsuitename then
	    args["testsuitename"] = testsuitename
    end

    ret = @server.call("tl.getTestCaseIDByName",args)
  end  
#
#=== 概要
#
# createTestCase()
#
#=== 引数
#
#=== 戻り値
#
#=== 例外
#
#=== 詳細
#
# テストケース名を作成する(未実装)
#
#=== 例
#
# createTestCase() #=>
#
  def createTestCase()#未実装
    args = {"devKey"=>@devKey}
    ret = @server.call("tl.createTestCase",args)
  end
#
#=== 概要
#
# createTestProject()
#
#=== 引数
#
#=== 戻り値
#
#=== 例外
#
#=== 詳細
#
# テストプロジェクトを作成する(未実装(引数不明))
#
#=== 例
#
# createTestProject() #=>
#
  def createTestProject()#引数不明
    args = {"devKey"=>@devKey}
    ret = @server.call("tl.createTestProject",args)
  end  
#
#=== 概要
#
# getTestCaseCustomFieldDesignValue()
#
#=== 引数
#
#=== 戻り値
#
#=== 例外
#
#=== 詳細
#
# カスタムフィールドの値を得る(使用方法不明)
#
#=== 例
#
# getTestCaseCustomFieldDesignValue() #=>
#
  def getTestCaseCustomFieldDesignValue()
    args = {"devKey"=>@devKey}
    ret = @server.call("tl.getTestCaseCustomFieldDesignValue",args)
  end  

#
#=== 概要
#
# about()
#
#=== 引数
#
#    なし
#
#=== 戻り値
#
# "Testlink API Version: 1.0 Beta 3 written by Asiel Brumfield\n contribution by TestLink development Team"
#
#=== 例外
#
#=== 詳細
#
# Testlink APIの情報が返る
#
#=== 例
#
# about() #=>Testlink API Version: 1.0 Beta 3 written by Asiel Brumfield\n contribution by TestLink development Team
#
  def about
    ret = @server.call("tl.about")
  end

#
#=== 概要
#
# sayHello()
#
#=== 引数
#
#    なし
#
#=== 戻り値
#
# "Hello!"
#
#=== 例外
#
#=== 詳細
#
# "Hello!"が返る
# 動作確認用
#
#=== 例
#
# sayHello() #=>"Hello!"
#
  def sayHello
    ret = @server.call("tl.sayHello")
  end

#
#=== 概要
#
# repeat(message)
#
#=== 引数
#
#+message+::
# メッセージ
#
#=== 戻り値
#
# "You said: xxxxx"
#
#=== 例外
#
#=== 詳細
#
# 送信したメッセージを返す
# 動作確認用
#
#=== 例
#
# repeat("message") #=>"You said: message"
#
#You said: message
  def repeat(message)
    args = {"str"=>message}
    ret = @server.call("tl.repeat",args)
  end


######################################
#
#  以下独自関数
#
######################################



#
#=== 概要
#
# getProjectID
#
#=== 引数
#
# なし
#
#=== 戻り値
#
# 最も大きい（最新の）IDでアクティブなテストプロジェクトID
# 例 3
#
#=== 詳細
#
# 最も大きい（最新の）IDでアクティブなテストプロジェクトIDが返ります
#
#=== 例
# getProjectID #=>3
#
  def getProjectID
    args = {"devKey"=>@devKey}
    ret = @server.call("tl.getProjects",args)
    val = ret.select{|x|x["active"]=="1"}.sort{|a,b|a["id"].to_i<=>b["id"].to_i}
    if not val.nil? then
        return val[-1]["id"]
    end
    raise "ProjectID error"
  end

#
#=== 概要
#
# getTestPlanID(pid)
#
#=== 引数
#
#+pid+::
# テストプロジェクトID
#
#=== 戻り値
#
# 最も大きい（最新の）IDでアクティブなテスト計画ID
# 例 3
#
#=== 詳細
#
# 最も大きい（最新の）IDでアクティブなテスト計画IDが返ります
#
#=== 例
# getTestPlanID(3) #=>4
#
  def getTestPlanID(pid)
    args = {"devKey"=>@devKey,"testprojectid"=>pid}
    ret = @server.call("tl.getProjectTestPlans",args)
    tpid=[]
    
    ret.each{|item|
      item.each{|key,val|
        tpid << key.to_i
      }
    }
    tpid.sort
    if tpid.size != 0 then
      return tpid[-1]
    end
    raise "TestPlanID error"
  end

#
#=== 概要
#
# getBuildID(tpid)
#
#=== 引数
#
#+tpid+::
# テスト計画ID
#
#=== 戻り値
#
# 最も大きい（最新の）IDでアクティブでオープンなビルドID
# 例 3
#
#=== 詳細
#
# 最も大きい（最新の）IDでアクティブでオープンなビルドIDが返ります
#
#=== 例
# getBuildID(tpid) #=>4
#
  def getBuildID(tpid)
    args = {"devKey"=>@devKey,"testplanid"=>tpid}     
    ret = @server.call("tl.getBuildsForTestPlan",args)
    val = ret.select{|x|x["active"]=="1" and x["is_open"]=="1" }.sort{|a,b|a["id"].to_i<=>b["id"].to_i}
    if not val.nil? then
        return val[-1]["id"]
    end
    raise "BuildID error"
  end  

#
#=== 概要
#
# reportTCResultByTCName(testcasename,testsuitename,status)
#
#=== 引数
#
#+testcasename+::
# テストケース名
#+testsuitename+::
# テストスイート名
#+status+::
#  テスト結果 p:成功 f:失敗 b:ブロック
#
#=== 戻り値
#
# 成功・失敗を返す
# 例 [{"message"=>"Success!", "status"=>true, "id"=>"1638"}]
#
#=== 詳細
#
# テストケース名及びテストスイート名で指定したテストケースに試験結果を登録する
#
#=== 例
# reportTCResultByTCName(testcasename,testsuitename,"p") #=>[{"message"=>"Success!", "status"=>true, "id"=>"1638"}]
#
  def reportTCResultByTCName(testcasename,testsuitename,status)
    pid = getProjectID
    tpid = getTestPlanID(pid)
    bid = getBuildID(tpid)
    ret = getTestCaseIDByName(testcasename,testsuitename)
    tcInfo = ret[0]
    tcid = tcInfo["id"]
    ret = reportTCResult(tpid,tcid,status,bid)
  end


#
#=== 概要
#
# getStatusByTCName(testcasename,testsuitename)
#
#=== 引数
#
#+testcasename+::
# テストケース名
#+testsuitename+::
# テストスイート名
#
#=== 戻り値
#
#  テスト結果 p:成功 f:失敗 b:ブロック を返す
#
#=== 詳細
#
# テストケース名及びテストスイート名でテストケースの試験結果を得る
#
#=== 例
# getStatusByTCName(testcasename,testsuitename,status) #=>"p"
#
  def getStatusByTCName(testcasename,testsuitename)
    pid = getProjectID
    tpid = getTestPlanID(pid)
    bid = getBuildID(tpid)
    ret = getTestCaseIDByName(testcasename,testsuitename,status)
    tcInfo = ret[0]
    tcid = tcInfo["id"]
    ret = getTestCasesForTestPlan(tpid,tcid)
    ret.each{|key,val|
      return val["exec_status"]
    }
  end
end
 
