znt/module/testcase/model.php

3011 lines
114 KiB
PHP
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
/**
* The model file of case module of ZenTaoPMS.
*
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Chunsheng Wang <chunsheng@cnezsoft.com>
* @package case
* @version $Id: model.php 5108 2013-07-12 01:59:04Z chencongzhi520@gmail.com $
* @link https://www.zentao.net
*/
?>
<?php
class testcaseModel extends model
{
/**
* 设置菜单。
* Set menu.
*
* @param int $productID
* @param int|string $branch
* @access public
* @return void
*/
public function setMenu(int $productID, int|string $branch = 0): void
{
$this->loadModel('qa')->setMenu($productID, $branch);
}
/**
* 创建一个用例。
* Create a case.
*
* @param object $case
* @access public
* @return bool|int
*/
public function create(object $case): bool|int
{
/* 插入测试用例。 */
/* Insert testcase. */
$this->testcaseTao->doCreate($case);
if(dao::isError()) return false;
$caseID = $this->dao->lastInsertID();
/* 记录动态。*/
/* Record log. */
$this->loadModel('action');
$this->action->create('case', $caseID, 'Opened');
if($case->status == 'wait') $this->action->create('case', $caseID, 'submitReview');
/* 存储上传的文件。 */
/* Save upload files. */
$this->config->dangers = '';
$this->loadModel('file')->saveUpload('testcase', $caseID, 'autoscript', 'script', 'scriptName');
$files = $this->loadModel('file')->saveUpload('testcase', $caseID);
$this->testcaseTao->doCreateSpec($caseID, $case, $files);
$this->loadModel('score')->create('testcase', 'create', $caseID);
/* 插入用例步骤。 */
/* Insert testcase steps. */
$this->testcaseTao->insertSteps($caseID, $case->steps, $case->expects, $case->stepType);
if(isset($case->auto) && $case->auto != 'auto')
{
ob_start();
setcookie('onlyAutoCase', '0');
}
if(dao::isError()) return false;
return $caseID;
}
/**
* 获取模块的用例。
* Get cases of modules.
*
* @param int|array $productID
* @param int|string $branch
* @param int|array $moduleIdList
* @param string $browseType
* @param string $auto no|unit
* @param string $caseType
* @param string $orderBy
* @param object $pager
* @param string $from
* @access public
* @return array
*/
public function getModuleCases(int|array $productID, int|string $branch = 0, int|array $moduleIdList = 0, string $browseType = '', string $auto = 'no', string $caseType = '', string $orderBy = 'id_desc', ?object $pager = null, string $from = 'testcase'): array
{
$isProjectTab = $this->app->tab == 'project' && $from != 'doc';
$isExecutionTab = $this->app->tab == 'execution' && $from != 'doc';
$stmt = $this->dao->select('t1.*, t2.title as storyTitle, t2.deleted as storyDeleted')->from(TABLE_CASE)->alias('t1')
->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story=t2.id');
if($isProjectTab) $stmt = $stmt->leftJoin(TABLE_PROJECTCASE)->alias('t3')->on('t1.id=t3.case');
if($isExecutionTab) $stmt = $stmt->leftJoin(TABLE_PROJECTCASE)->alias('t3')->on('t1.id=t3.case');
return $stmt ->where('t1.product')->in($productID)
->beginIF($isProjectTab)->andWhere('t3.project')->eq($this->session->project)->fi()
->beginIF($isExecutionTab)->andWhere('t3.project')->eq($this->session->execution)->fi()
->beginIF($branch !== 'all')->andWhere('t1.branch')->eq($branch)->fi()
->beginIF($moduleIdList)->andWhere('t1.module')->in($moduleIdList)->fi()
->beginIF($browseType == 'wait')->andWhere('t1.status')->eq($browseType)->fi()
->beginIF($browseType == 'needconfirm')
->andWhere('t2.version > t1.storyVersion')
->andWhere('t2.status')->eq('active')
->fi()
->beginIF($auto == 'auto' || $auto == 'unit')->andWhere('t1.auto')->eq($auto)->fi()
->beginIF($auto != 'auto' && $auto != 'unit')->andWhere('t1.auto')->ne('unit')->fi()
->beginIF($caseType)->andWhere('t1.type')->eq($caseType)->fi()
->andWhere('t1.deleted')->eq('0')
->orderBy($orderBy)
->page($pager)
->fetchAll('id', false);
}
/**
* 获取执行的用例。
* Get execution cases.
*
* @param string $browseType all|wait|needconfirm
* @param int $executionID
* @param int $productID
* @param int $branchID
* @param int $moduleID
* @param int $paramID
* @param string $orderBy
* @param object $pager
* @access public
* @return array
*/
public function getExecutionCases(string $browseType = 'all', int $executionID = 0, int $productID = 0, int|string $branchID = 0, int $moduleID = 0, int $paramID = 0, string $orderBy = 'id_desc', ?object $pager = null): array
{
if(common::isTutorialMode()) return $this->loadModel('tutorial')->getCases();
if(strpos($orderBy, 'id_') !== false) $orderBy = str_replace('id_', 't2.id_', $orderBy);
if(strpos($orderBy, 'order_') !== false) $orderBy = str_replace('order_', 't2.order_', $orderBy);
if(strpos($orderBy, 'version_') !== false) $orderBy = str_replace('version_', 't2.version_', $orderBy);
if($browseType == 'needconfirm')
{
return $this->dao->select('distinct t1.*, t2.*')->from(TABLE_PROJECTCASE)->alias('t1')
->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
->leftJoin(TABLE_STORY)->alias('t3')->on('t2.story = t3.id')
->leftJoin(TABLE_MODULE)->alias('t4')->on('t2.module = t4.id')
->where('t1.project')->eq($executionID)
->andWhere('t2.deleted')->eq('0')
->andWhere('t3.version > t2.storyVersion')
->andWhere("t3.status")->eq('active')
->beginIF($productID)->andWhere('t1.product')->eq($productID)->fi()
->beginIF($productID && $branchID !== 'all')->andWhere('t2.branch')->eq($branchID)->fi()
->beginIF($moduleID)->andWhere('t4.path')->like("%,$moduleID,%")->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll('id', false);
}
if($browseType == 'bysearch') return $this->testcaseTao->getExecutionCasesBySearch($executionID, $productID, $branchID, $paramID, $orderBy, $pager);
return $this->dao->select('distinct t1.*, t2.*')->from(TABLE_PROJECTCASE)->alias('t1')
->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
->leftJoin(TABLE_MODULE)->alias('t3')->on('t2.module = t3.id')
->where('t1.project')->eq($executionID)
->andWhere('t2.deleted')->eq('0')
->beginIF($browseType != 'all' && $browseType != 'byModule')->andWhere('t2.status')->eq($browseType)->fi()
->beginIF($productID)->andWhere('t1.product')->eq($productID)->fi()
->beginIF($productID && $branchID !== 'all')->andWhere('t2.branch')->eq($branchID)->fi()
->beginIF($moduleID)->andWhere('t3.path')->like("%,$moduleID,%")->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll('id', false);
}
/**
* Get case info by ID.
*
* @param int $caseID
* @param int $version
* @access public
* @return object|bool
*/
public function getById(int $caseID, int $version = 0): object|bool
{
if(common::isTutorialMode()) return $this->loadModel('tutorial')->getCase();
$case = $this->dao->findById($caseID)->from(TABLE_CASE)->fetch();
if(!$case) return false;
$case = $this->loadModel('file')->replaceImgURL($case, 'steps');
$case = $this->processDateField($case);
/* Get project and execution. */
if($this->app->tab == 'project')
{
$case->project = $this->session->project;
}
elseif($this->app->tab == 'execution')
{
$case->execution = $this->session->execution;
$case->project = $this->dao->select('project')->from(TABLE_PROJECT)->where('id')->eq($case->execution)->fetch('project');
}
else
{
$objects = $this->dao->select('t1.project as objectID, t2.type')->from(TABLE_PROJECTCASE)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.project=t2.id')
->where('t1.case')->eq($caseID)
->fetchPairs();
foreach($objects as $objectID => $objectType)
{
if($objectType == 'project') $case->project = $objectID;
if(in_array($objectType, array('sprint', 'stage', 'kanban'))) $case->execution = $objectID;
}
}
/* Set related variables. */
$toBugs = $this->dao->select('id, title, severity, openedDate, status')->from(TABLE_BUG)->where('`case`')->eq($caseID)->fetchAll();
$case->toBugs = array();
$case->fromBugData = array();
$case->linkCaseTitles = array();
foreach($toBugs as $toBug) $case->toBugs[$toBug->id] = $toBug;
if($case->story)
{
$story = $this->dao->findById($case->story)->from(TABLE_STORY)->fields('title, status, version')->fetch();
$case->storyTitle = $story->title;
$case->storyStatus = $story->status;
$case->latestStoryVersion = $story->version;
}
if($case->fromBug) $case->fromBugData = $this->dao->findById($case->fromBug)->from(TABLE_BUG)->fields('id, title, severity, openedDate, status')->fetch();
if($case->linkCase || $case->fromCaseID) $case->linkCaseTitles = $this->dao->select('id,title')->from(TABLE_CASE)->where('id')->in($case->linkCase)->orWhere('id')->eq($case->fromCaseID)->fetchPairs();
$case->currentVersion = $version ? $version : $case->version;
$case->files = $this->loadModel('file')->getByObject('testcase', $caseID);
$case->steps = $this->testcaseTao->getSteps($caseID, $case->currentVersion);
$spec = $this->dao->select('title,precondition,files')->from(TABLE_CASESPEC)->where('case')->eq($caseID)->andWhere('version')->eq($version)->fetch();
if($spec)
{
$case->title = $spec->title;
$case->precondition = $spec->precondition;
$case->files = $this->file->getByIdList($spec->files);
}
return $case;
}
/**
* 测试通过 id 列表和查询语句获取用例。
* Get cases by id list and query string.
*
* @param array $caseIdList
* @param string $query
* @access public
* @return array
*/
public function getByList(array $caseIdList, string $query = ''): array
{
if(!$caseIdList) return array();
return $this->dao->select('*,precondition')->from(TABLE_CASE)
->where('deleted')->eq(0)
->andWhere('id')->in($caseIdList)
->beginIF($query)->andWhere($query)->fi()
->fetchAll('id', false);
}
/**
* 获取用例列表。
* Get test cases.
*
* @param int|array $productID
* @param int|string $branch
* @param string $browseType
* @param int $queryID
* @param int $moduleID
* @param string $caseType
* @param string $auto no|unit
* @param string $orderBy
* @param object $pager
* @param string $from
* @access public
* @return array
*/
public function getTestCases(int|array $productID, string|int $branch, string $browseType, int $queryID, int $moduleID, string $caseType = '', string $auto = 'no', string $orderBy = 'id_desc', ?object $pager = null, string $from = 'testcase'): array
{
if(common::isTutorialMode()) return $this->loadModel('tutorial')->getCases();
$modules = $moduleID ? $this->loadModel('tree')->getAllChildId($moduleID) : array();
$browseType = ($browseType == 'bymodule' and $this->session->caseBrowseType and $this->session->caseBrowseType != 'bysearch') ? $this->session->caseBrowseType : $browseType;
$auto = $this->cookie->onlyAutoCase ? 'auto' : $auto;
if($browseType == 'bymodule' || $browseType == 'all' || $browseType == 'wait')
{
if($this->app->tab == 'project' && $from != 'doc') return $this->testcaseTao->getModuleProjectCases($productID, $branch, $modules, $browseType, $auto, $caseType, $orderBy, $pager);
return $this->getModuleCases($productID, $branch, $modules, $browseType, $auto, $caseType, $orderBy, $pager, $from);
}
if($browseType == 'needconfirm') return $this->testcaseTao->getNeedConfirmList($productID, $branch, $modules, $auto, $caseType, $orderBy, $pager);
if($browseType == 'bysuite') return $this->testcaseTao->getBySuite($productID, $branch, (int)$queryID, $modules, $auto, $orderBy, $pager);
if($browseType == 'bysearch') return $this->getBySearch($productID, $branch, (int)$queryID, $auto, $orderBy, $pager);
return array();
}
/**
* 搜索用例。
* Get cases by search.
*
* @param int $productID
* @param int|string $branch
* @param int $queryID
* @param string $auto no|unit
* @param string $orderBy
* @param object $pager
* @access public
* @return array
*/
public function getBySearch(int $productID, int|string $branch = 0, int $queryID = 0, string $auto = 'no', string $orderBy = 'id_desc', ?object $pager = null): array
{
$queryID = (int)$queryID;
if($queryID)
{
$query = $this->loadModel('search')->getQuery($queryID);
if($query)
{
$this->session->set('testcaseQuery', $query->sql);
$this->session->set('testcaseForm', $query->form);
}
}
if($this->session->testcaseQuery == false) $this->session->set('testcaseQuery', ' 1 = 1');
$caseQuery = '(' . $this->session->testcaseQuery;
// 将caseQuery中的字段替换成t1.字段
$caseQuery = preg_replace('/`(.*?)`/', 't1.`$1`', $caseQuery);
/* 处理用例查询中的产品条件。*/
/* Process product condition in case query. */
$queryProductID = $productID;
if(strpos($this->session->testcaseQuery, "`product` = 'all'") !== false)
{
$caseQuery = str_replace("t1.`product` = 'all'", '1', $caseQuery);
$caseQuery .= ' AND t1.`product` ' . helper::dbIN($this->app->user->view->products);
$queryProductID = 'all';
}
if($this->app->tab == 'project') $caseQuery = str_replace('t1.`product`', 't2.`product`', $caseQuery);
/* 处理用例查询中的产品分支条件。*/
/* Process branch condition in case query. */
if($branch !== 'all' && strpos($caseQuery, '`branch` =') === false) $caseQuery .= " AND t1.`branch` in ('$branch')";
if(strpos($caseQuery, "`branch` = 'all'") !== false) $caseQuery = str_replace("t1.`branch` = 'all'", '1 = 1', $caseQuery);
$caseQuery .= ')';
/* Search criteria under compatible project. */
$sql = $this->dao->select('DISTINCT t1.*,t3.title as storyTitle')->from(TABLE_CASE)->alias('t1');
if($this->app->tab == 'project') $sql->leftJoin(TABLE_PROJECTCASE)->alias('t2')->on('t1.id = t2.case');
$sql->leftJoin(TABLE_STORY)->alias('t3')->on('t1.story = t3.id');
return $sql->where($caseQuery)
->beginIF($this->app->tab == 'project' && $this->config->systemMode == 'ALM')->andWhere('t2.project')->eq($this->session->project)->fi()
->beginIF($this->app->tab == 'project' && !empty($productID) && $queryProductID != 'all')->andWhere('t2.product')->eq($productID)->fi()
->beginIF($this->app->tab != 'project' && !empty($productID) && $queryProductID != 'all')->andWhere('t1.product')->eq($productID)->fi()
->beginIF($auto == 'auto' || $auto == 'unit')->andWhere('t1.auto')->eq($auto)->fi()
->beginIF($auto != 'auto' && $auto != 'unit')->andWhere('t1.auto')->ne('unit')->fi()
->andWhere('t1.deleted')->eq('0')
->orderBy($orderBy)
->page($pager, 't1.id')
->fetchAll('id', false);
}
/**
* 根据指派给获取用例。
* Get cases by assignedTo.
*
* @param string $account
* @param string $auto no|unit|skip
* @param string $orderBy
* @param object $pager
* @access public
* @return array
*/
public function getByAssignedTo(string $account, string $auto = 'no', string $orderBy = 'id_desc', ?object $pager = null): array
{
return $this->dao->select('t1.id AS run, t1.task, t1.case, t1.version, t1.assignedTo, t1.lastRunner, t1.lastRunDate, t1.lastRunResult, t1.status AS lastRunStatus, t2.id AS id, t2.project, t2.pri, t2.title, t2.type, t2.openedBy, t2.color, t2.product, t2.lib, t2.branch, t2.module, t2.status, t2.auto, t2.story, t2.storyVersion, t3.name AS taskName')->from(TABLE_TESTRUN)->alias('t1')
->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case = t2.id')
->leftJoin(TABLE_TESTTASK)->alias('t3')->on('t1.task = t3.id')
->where('t1.assignedTo')->eq($account)
->andWhere('t3.deleted')->eq('0')
->andWhere('t2.deleted')->eq('0')
->andWhere('t3.status')->ne('done')
->beginIF(strpos($auto, 'skip') === false && $auto != 'unit')->andWhere('t2.auto')->ne('unit')->fi()
->beginIF($auto == 'unit')->andWhere('t2.auto')->eq('unit')->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll(strpos($auto, 'run') !== false ? 'run' : 'id');
}
/**
* 根据创建者获取用例。
* Get cases by openedBy
*
* @param string $account
* @param string $auto no|unit|skip
* @param string $orderBy
* @param object $pager
* @access public
* @return array
*/
public function getByOpenedBy(string $account, string $auto = 'no', string $orderBy = 'id_desc', ?object $pager = null): array
{
return $this->dao->findByOpenedBy($account)->from(TABLE_CASE)
->andWhere('deleted')->eq('0')
->beginIF($auto != 'skip')->andWhere('product')->ne(0)->fi()
->beginIF($auto != 'skip' && $auto != 'unit')->andWhere('auto')->ne('unit')->fi()
->beginIF($auto == 'unit')->andWhere('auto')->eq('unit')->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll('id', false);
}
/**
* 根据状态获取用例。
* Get cases by status.
*
* @param int $productID
* @param int|string $branch
* @param string $type all|needconfirm
* @param string $status all|normal|blocked|investigate
* @param int $moduleID
* @param string $auto no|unit|skip
* @param string $orderBy
* @param object $pager
* @access public
* @return array
*/
public function getByStatus(int $productID = 0, int|string $branch = 0, string $type = 'all', string $status = 'all', int $moduleID = 0, string $auto = 'no', string $orderBy = 'id_desc', ?object $pager = null): array
{
$modules = $moduleID ? $this->loadModel('tree')->getAllChildId($moduleID) : '0';
$cases = $this->dao->select('t1.*, t2.title AS storyTitle')->from(TABLE_CASE)->alias('t1')
->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
->where('1=1')
->beginIF($productID)->andWhere('t1.product')->eq((int) $productID)->fi()
->beginIF(!$productID)->andWhere('t1.product')->in($this->app->user->view->products)->fi()
->beginIF($branch)->andWhere('t1.branch')->eq($branch)->fi()
->beginIF($modules)->andWhere('t1.module')->in($modules)->fi()
->beginIF($type == 'needconfirm')
->andWhere('t2.status')->eq('active')
->andWhere('t2.version > t1.storyVersion')
->fi()
->beginIF($status != 'all')->andWhere('t1.status')->eq($status)->fi()
->beginIF($auto == 'unit')->andWhere('t1.auto')->eq('unit')->fi()
->beginIF($auto != 'unit')->andWhere('t1.auto')->ne('unit')->fi()
->andWhere('t1.deleted')->eq('0')
->orderBy($orderBy)
->page($pager)
->fetchAll('id', false);
return $this->appendData($cases);
}
/**
* 根据产品获取用例。
* Get cases by product id.
*
* @param int $productID
* @access public
* @return array
*/
public function getByProduct(int $productID): array
{
return $this->dao->select('*')->from(TABLE_CASE)->where('deleted')->eq('0')->andWhere('product')->eq($productID)->fetchAll('id', false);
}
/**
* 通过产品 id 和分支获取用例键对。
* Get case pairs by product id and branch.
*
* @param int $productID
* @param int|array $branch
* @param string $search
* @param int $limit
* @access public
* @return array
*/
public function getPairsByProduct(int $productID, int|array $branch = 0, string $search = '', int $limit = 0): array
{
return $this->dao->select("id, concat_ws(':', id, title) as title")->from(TABLE_CASE)
->where('deleted')->eq(0)
->andWhere('product')->eq($productID)
->beginIF($branch)->andWhere('branch')->in($branch)->fi()
->beginIF(strlen(trim($search)))->andWhere('title')->like('%' . $search . '%')->fi()
->orderBy('id_desc')
->beginIF($limit)->limit($limit)->fi()
->fetchPairs();
}
/**
* 获取关联需求的用例。
* Get cases of a story.
*
* @param int $storyID
* @access public
* @return array
*/
public function getStoryCases(int $storyID): array
{
return $this->dao->select('id, project, title, pri, type, status, lastRunner, lastRunDate, lastRunResult')
->from(TABLE_CASE)
->where('story')->eq((int)$storyID)
->andWhere('deleted')->eq(0)
->fetchAll('id');
}
/**
* 获取需求列表关联的用例数量数组。
* Get counts of some stories' cases.
*
* @param array $stories
* @access public
* @return array
*/
public function getStoryCaseCounts(array $stories): array
{
if(empty($stories)) return array();
$caseCounts = $this->dao->select('story, COUNT(1) AS cases')
->from(TABLE_CASE)
->where('story')->in($stories)
->andWhere('deleted')->eq(0)
->groupBy('story')
->fetchPairs();
foreach($stories as $storyID)
{
if(!isset($caseCounts[$storyID])) $caseCounts[$storyID] = 0;
}
return $caseCounts;
}
/**
* 获取导出的用例。
* Get cases to export.
*
* @param string $exportType
* @param int $taskID
* @param string $orderBy
* @param int $limit
* @access public
* @return array
*/
public function getCasesToExport(string $exportType, int $taskID, string $orderBy, int $limit): array
{
if(strpos($orderBy, 'case') !== false)
{
list($field, $sort) = explode('_', $orderBy);
$orderBy = '`' . $field . '`_' . $sort;
}
$queryCondition = preg_replace("/AND\s+t[0-9]\.scene\s+=\s+'0'/i", '', $this->session->testcaseQueryCondition);
if($this->session->testcaseOnlyCondition)
{
$caseIdList = array();
if($taskID) $caseIdList = $this->dao->select('`case`')->from(TABLE_TESTRUN)->where('task')->eq($taskID)->fetchPairs();
return $this->dao->select('*')->from(TABLE_CASE)->where($queryCondition)
->beginIF($taskID)->andWhere('id')->in($caseIdList)->fi()
->beginIF($exportType == 'selected')->andWhere('id')->in($this->cookie->checkedItem)->fi()
->orderBy($orderBy)
->beginIF($limit)->limit($limit)->fi()
->fetchAll('id', false);
}
$cases = array();
$orderBy = " ORDER BY " . str_replace(array('|', '^A', '_'), ' ', $orderBy);
$stmt = $this->dao->query($queryCondition . $orderBy . ($limit ? ' LIMIT ' . $limit : ''));
while($row = $stmt->fetch())
{
$caseID = isset($row->case) ? $row->case : $row->id;
if($exportType == 'selected' && $taskID && strpos(",{$this->cookie->checkedItem},", ",$row->id,") === false) continue;
if($exportType == 'selected' && !$taskID && strpos(",{$this->cookie->checkedItem},", ",$caseID,") === false) continue;
$row->id = $caseID;
$cases[$caseID] = $row;
}
return $cases;
}
/**
* 获取导出的用例的结果。
* Get case results for export.
*
* @param array $caseIdList
* @param int $taskID
* @access public
* @return array
*/
public function getCaseResultsForExport(array $caseIdList, int $taskID = 0): array
{
$stmt = $this->dao->select('t1.*')->from(TABLE_TESTRESULT)->alias('t1')
->leftJoin(TABLE_TESTRUN)->alias('t2')->on('t1.run = t2.id')
->where('t1.`case`')->in($caseIdList)
->beginIF($taskID)->andWhere('t2.task')->eq($taskID)->fi()
->beginIF($this->app->tab == 'devops')->andWhere('t1.deploy')->eq($this->session->deployID)->fi()
->orderBy('id_desc')
->query();
$results = array();
while($result = $stmt->fetch())
{
if(!isset($results[$result->case])) $results[$result->case] = unserialize($result->stepResults);
}
return $results;
}
/**
* 更新用例。
* Update a case.
*
* @param object $case
* @param object $oldCase
* @param array $testtasks
* @access public
* @return bool|array
*/
public function update(object $case, object $oldCase, array $testtasks = array()): bool|array
{
$this->testcaseTao->doUpdate($case);
if(dao::isError()) return false;
$this->testcaseTao->updateCase2Project($oldCase, $case);
if($oldCase->version != $case->version || !empty($case->stepChanged)) $this->testcaseTao->updateStep($case, $oldCase);
if(isset($oldCase->toBugs) && isset($case->linkBug)) $this->testcaseTao->linkBugs($oldCase->id, array_keys($oldCase->toBugs), $case);
if($case->branch && !empty($testtasks)) $this->testcaseTao->unlinkCaseFromTesttask($oldCase->id, $case->branch, $testtasks);
$this->loadModel('file')->processFileDiffsForObject('testcase', $oldCase, $case);
if($oldCase->version != $case->version) $this->testcaseTao->doCreateSpec($oldCase->id, $case, $case->files);
/* Join the steps to diff. */
if(!empty($case->stepChanged) && $case->steps)
{
$oldCase->steps = $this->joinStep($oldCase->steps);
$case->steps = $this->joinStep($this->getByID($oldCase->id, $case->version)->steps);
}
else
{
unset($oldCase->steps);
unset($case->steps);
}
return common::createChanges($oldCase, $case);
}
/**
* 评审用例。
* Review case.
*
* @param object $case
* @param object $oldCase
* @access public
* @return bool
*/
public function review(object $case, object $oldCase): bool
{
$this->dao->update(TABLE_CASE)->data($case, 'result,comment')->autoCheck()->checkFlow()->where('id')->eq($oldCase->id)->exec();
if(dao::isError()) return false;
$changes = common::createChanges($oldCase, $case);
$actionID = $this->loadModel('action')->create('case', $oldCase->id, 'Reviewed', $this->post->comment, ucfirst($case->result));
$this->action->logHistory($actionID, $changes);
return true;
}
/**
* Batch review cases.
*
* @param array $caseIdList
* @param string $result
* @access public
* @return bool
*/
public function batchReview(array $caseIdList, string $result): bool
{
$caseIdList = array_filter($caseIdList);
if(!$caseIdList) return false;
$oldCases = $this->getByList($caseIdList, "status = 'wait'");
if(!$oldCases) return false;
$now = helper::now();
$case = new stdClass();
$case->reviewedBy = $this->app->user->account;
$case->reviewedDate = substr($now, 0, 10);
$case->lastEditedBy = $this->app->user->account;
$case->lastEditedDate = $now;
if($result == 'pass') $case->status = 'normal';
$this->dao->update(TABLE_CASE)->data($case)->autoCheck()->where('id')->in(array_keys($oldCases))->exec();
if(dao::isError()) return false;
$this->loadModel('action');
foreach($oldCases as $oldCase)
{
$changes = common::createChanges($oldCase, $case);
if($changes)
{
$actionID = $this->action->create('case', $oldCase->id, 'Reviewed', '', ucfirst($result));
$this->action->logHistory($actionID, $changes);
}
}
return !dao::isError();
}
/**
* 获取可关联的用例。
* Get cases to link.
*
* @param int $caseID
* @param string $browseType
* @param int $queryID
* @access public
* @return array
*/
public function getCases2Link(int $caseID, string $browseType = 'bySearch', int $queryID = 0): array
{
if($browseType != 'bySearch') return array();
$case = $this->getByID($caseID);
$cases2Link = $this->getBySearch($case->product, $case->branch, (int)$queryID, $auto = 'no', $orderBy = 'id');
foreach($cases2Link as $key => $case2Link)
{
if($case2Link->id == $caseID) unset($cases2Link[$key]);
if(in_array($case2Link->id, explode(',', $case->linkCase))) unset($cases2Link[$key]);
}
return $cases2Link;
}
/**
* 获取可关联的 bug。
* Get bugs to link.
*
* @param int $caseID
* @param string $browseType
* @param int $queryID
* @param string $orderBy
* @access public
* @return array
*/
public function getBugs2Link(int $caseID, string $browseType = 'bySearch', int $queryID = 0, string $orderBy = 'id_asc'): array
{
if($browseType != 'bySearch') return array();
$case = $this->getByID($caseID);
$bugs2Link = $this->loadModel('bug')->getBySearch('bug', $case->product, (string)$case->branch, 0, 0, (int)$queryID, '', $orderBy);
foreach($bugs2Link as $key => $bug2Link)
{
if($bug2Link->case != 0) unset($bugs2Link[$key]);
}
return $bugs2Link;
}
/**
* 批量删除用例和场景。
* Batch delete cases and scenes.
*
* @param array $caseIdList
* @param array $sceneIdList
* @access public
* @return bool
*/
public function batchDelete(array $caseIdList, array $sceneIdList): bool
{
/* 过滤用例和场景。 */
/* Filter cases and scenes. */
$caseIdList = array_filter($caseIdList);
$sceneIdList = array_filter($sceneIdList);
if(!$caseIdList && !$sceneIdList) return false;
$this->loadModel('action');
/* 删除用例。 */
/* Delete cases. */
if($caseIdList)
{
$this->dao->update(TABLE_CASE)->set('deleted')->eq('1')->where('id')->in($caseIdList)->exec();
foreach($caseIdList as $caseID) $this->action->create('case', (int)$caseID, 'deleted', '', actionModel::CAN_UNDELETED);
}
/* 删除场景。 */
/* Delete scenes. */
if($sceneIdList)
{
$this->dao->update(TABLE_SCENE)->set('deleted')->eq('1')->where('id')->in($sceneIdList)->exec();
foreach($sceneIdList as $sceneID) $this->action->create('scene', $sceneID, 'deleted', '', actionModel::CAN_UNDELETED);
}
return !dao::isError();
}
/**
* Batch change branch of cases and scenes.
*
* @param array $caseIdList
* @param array $sceneIdList
* @param int $branchID
* @access public
* @return bool
*/
public function batchChangeBranch(array $caseIdList, array $sceneIdList, int $branchID): bool
{
if($branchID < 0 || $branchID > 16777215) return false; // The branch column's data type is mediumint unsigned and its range is 0-16777215.
$caseIdList = array_filter($caseIdList);
$sceneIdList = array_filter($sceneIdList);
if(!$caseIdList && !$sceneIdList) return false;
if($caseIdList) $this->batchChangeCaseBranch($caseIdList, $branchID);
if($sceneIdList) $this->batchChangeSceneBranch($sceneIdList, $branchID);
return !dao::isError();
}
/**
* Batch change branch of cases.
*
* @param array $caseIdList
* @param int $branchID
* @access public
* @return bool
*/
public function batchChangeCaseBranch(array $caseIdList, int $branchID): bool
{
if(!$caseIdList) return false;
if($branchID < 0 || $branchID > 16777215) return false; // The branch column's data type is mediumint unsigned and its range is 0-16777215.
$oldCases = $this->getByList($caseIdList, "branch != '{$branchID}'");
if(!$oldCases) return false;
$case = new stdclass();
$case->branch = $branchID;
$case->lastEditedBy = $this->app->user->account;
$case->lastEditedDate = helper::now();
$this->dao->update(TABLE_CASE)->data($case)->where('id')->in(array_keys($oldCases))->exec();
if(dao::isError()) return false;
$this->loadModel('action');
foreach($oldCases as $oldCase)
{
$changes = common::createChanges($oldCase, $case);
if($changes)
{
$actionID = $this->action->create('case', $oldCase->id, 'edited');
$this->action->logHistory($actionID, $changes);
}
}
return !dao::isError();
}
/**
* Batch change branch of scenes.
*
* @param array $sceneIdList
* @param int $branchID
* @access public
* @return bool
*/
public function batchChangeSceneBranch(array $sceneIdList, int $branchID): bool
{
if(!$sceneIdList) return false;
if($branchID < 0 || $branchID > 16777215) return false; // The branch column's data type is mediumint unsigned and its range is 0-16777215.
$oldScenes = $this->getScenesByList($sceneIdList, "branch != '{$branchID}'");
if(!$oldScenes) return false;
$scene = new stdclass();
$scene->branch = $branchID;
$scene->lastEditedBy = $this->app->user->account;
$scene->lastEditedDate = helper::now();
$this->dao->update(TABLE_SCENE)->data($scene)->where('id')->in(array_keys($oldScenes))->exec();
if(dao::isError()) return false;
$this->loadModel('action');
foreach($oldScenes as $oldScene)
{
$changes = common::createChanges($oldScene, $scene);
if($changes)
{
$actionID = $this->action->create('scene', $oldScene->id, 'edited');
$this->action->logHistory($actionID, $changes);
}
}
return !dao::isError();
}
/**
* Batch change module of cases and scenes.
*
* @param array $caseIdList
* @param array $sceneIdList
* @param int $moduleID
* @access public
* @return bool
*/
public function batchChangeModule(array $caseIdList, array $sceneIdList, int $moduleID): bool
{
if($moduleID < 0 || $moduleID > 16777215) return false; // The module column's data type is mediumint unsigned and its range is 0-16777215.
$caseIdList = array_filter($caseIdList);
$sceneIdList = array_filter($sceneIdList);
if(!$caseIdList && !$sceneIdList) return false;
if($caseIdList) $this->batchChangeCaseModule($caseIdList, $moduleID);
if($sceneIdList) $this->batchChangeSceneModule($sceneIdList, $moduleID);
return !dao::isError();
}
/**
* Batch change module of cases.
*
* @param array $caseIdList
* @param int $moduleID
* @access public
* @return bool
*/
public function batchChangeCaseModule(array $caseIdList, int $moduleID): bool
{
if(!$caseIdList) return false;
if($moduleID < 0 || $moduleID > 16777215) return false; // The module column's data type is mediumint unsigned and its range is 0-16777215.
$oldCases = $this->getByList($caseIdList, "module != '{$moduleID}'");
if(!$oldCases) return false;
$case = new stdclass();
$case->module = $moduleID;
$case->lastEditedBy = $this->app->user->account;
$case->lastEditedDate = helper::now();
$this->dao->update(TABLE_CASE)->data($case)->where('id')->in(array_keys($oldCases))->exec();
if(dao::isError()) return false;
$this->loadModel('action');
foreach($oldCases as $oldCase)
{
$changes = common::createChanges($oldCase, $case);
if($changes)
{
$actionID = $this->action->create('case', $oldCase->id, 'edited');
$this->action->logHistory($actionID, $changes);
}
}
return !dao::isError();
}
/**
* Batch change module of scenes.
*
* @param array $sceneIdList
* @param int $moduleID
* @access public
* @return bool
*/
public function batchChangeSceneModule(array $sceneIdList, int $moduleID): bool
{
if(!$sceneIdList) return false;
if($moduleID < 0 || $moduleID > 16777215) return false; // The module column's data type is mediumint unsigned and its range is 0-16777215.
$oldScenes = $this->getScenesByList($sceneIdList, "module != '{$moduleID}'");
if(!$oldScenes) return false;
$scene = new stdclass();
$scene->module = $moduleID;
$scene->lastEditedBy = $this->app->user->account;
$scene->lastEditedDate = helper::now();
$this->dao->update(TABLE_SCENE)->data($scene)->where('id')->in(array_keys($oldScenes))->exec();
if(dao::isError()) return false;
$this->loadModel('action');
foreach($oldScenes as $oldScene)
{
$changes = common::createChanges($oldScene, $scene);
if($changes)
{
$actionID = $this->action->create('scene', $oldScene->id, 'edited');
$this->action->logHistory($actionID, $changes);
}
}
return !dao::isError();
}
/**
* Batch change type of cases.
*
* @param array $caseIdList
* @param string $type
* @access public
* @return bool
*/
public function batchChangeType(array $caseIdList, string $type): bool
{
if(!$type) return false;
$caseIdList = array_filter($caseIdList);
if(!$caseIdList) return false;
$oldCases = $this->getByList($caseIdList, "type != '{$type}'");
if(!$oldCases) return false;
$case = new stdClass();
$case->type = $type;
$case->lastEditedBy = $this->app->user->account;
$case->lastEditedDate = helper::now();
$this->dao->update(TABLE_CASE)->data($case)->autoCheck()->where('id')->in(array_keys($oldCases))->exec();
if(dao::isError()) return false;
$this->loadModel('action');
foreach($oldCases as $oldCase)
{
$changes = common::createChanges($oldCase, $case);
if($changes)
{
$actionID = $this->action->create('case', $oldCase->id, 'Edited', '', ucfirst($type));
$this->action->logHistory($actionID, $changes);
}
}
return !dao::isError();
}
/**
* Batch confirm story change of cases.
*
* @param array $caseIdList
* @access public
* @return bool
*/
public function batchConfirmStoryChange(array $caseIdList): bool
{
$caseIdList = array_filter($caseIdList);
if(!$caseIdList) return false;
$cases = $this->getByList($caseIdList);
if(!$cases) return false;
$storyIdList = array_unique(array_filter(array_map(function($case){return $case->story;}, $cases)));
if(!$storyIdList) return false;
$stories = $this->dao->select('id, version')->from(TABLE_STORY)->where('id')->in($storyIdList)->fetchPairs();
$this->loadModel('action');
foreach($cases as $case)
{
$storyVersion = zget($stories, $case->story, 0);
if(!$storyVersion) continue;
$this->dao->update(TABLE_CASE)->set('storyVersion')->eq($storyVersion)->where('id')->eq($case->id)->exec();
$this->action->create('case', $case->id, 'confirmed', '', $storyVersion);
}
return !dao::isError();
}
/**
* 将步骤转为字符串,并且区分它们。
* Join steps to a string, thus can diff them.
*
* @param array $steps
* @access public
* @return string
*/
public function joinStep(array $steps): string
{
$return = '';
if(empty($steps)) return $return;
foreach($steps as $step) $return .= $step->desc . ' EXPECT:' . $step->expect . "\n";
return $return;
}
/**
* 从 bug 的步骤创建用例步骤。
* Create case steps from a bug's step.
*
* @param string $steps
* @access public
* @return array
*/
public function createStepsFromBug(string $steps): array
{
/* 初始化步骤相关变量,获取步骤的描述、结果和期望,以及他们在字符串中的位置。 */
/* Initializes the step variable, and get the desc, result and expect of the step and its position. */
$steps = strip_tags($steps);
$caseSteps = array((object)array('name' => '1', 'desc' => $steps, 'step' => $steps, 'expect' => '')); // the default steps before parse.
$lblStep = strip_tags($this->lang->bug->tplStep);
$lblResult = strip_tags($this->lang->bug->tplResult);
$lblExpect = strip_tags($this->lang->bug->tplExpect);
$lblStepPos = strpos($steps, $lblStep);
$lblResultPos = strpos($steps, $lblResult);
$lblExpectPos = strpos($steps, $lblExpect);
/* 如果 bug 的步骤没有描述、结果或者期望,返回默认步骤。 */
/* If steps don't have desc, result or expect, return default steps. */
if($lblStepPos === false || $lblResultPos === false || $lblExpectPos === false) return $caseSteps;
/* 计算描述和期望。 */
/* Process desc and expect. */
$caseSteps = substr($steps, $lblStepPos + strlen($lblStep), $lblResultPos - strlen($lblStep) - $lblStepPos);
$caseExpect = substr($steps, $lblExpectPos + strlen($lblExpect));
$caseSteps = trim($caseSteps);
$caseExpect = trim($caseExpect);
/* 计算步骤。 */
/* Process steps. */
$caseSteps = explode("\n", trim($caseSteps));
$stepCount = count($caseSteps);
foreach($caseSteps as $key => $caseStep)
{
$expect = $key + 1 == $stepCount ? $caseExpect : '';
$caseSteps[$key] = (object)array('name' => '1', 'desc' => trim($caseStep), 'step' => trim($caseStep), 'expect' => $expect, 'type' => 'item');
}
return $caseSteps;
}
/**
* 判断当前动作是否可以点击。
* Adjust the action is clickable.
*
* @param object $case
* @param string $action
* @access public
* @return bool
*/
public static function isClickable(object $case, string $action): bool
{
$canBeChanged = common::canBeChanged('case', $case);
if(!$canBeChanged) return false;
global $config;
$action = strtolower($action);
if($action == 'runcase') return (empty($case->lib) || !empty($case->product)) && $case->auto == 'no' && $case->status != 'wait';
if($action == 'runresult') return !$case->lib || !empty($case->product);
if($action == 'importtolib') return !$case->lib || !empty($case->product);
if($action == 'ztfrun') return $case->auto == 'auto';
if($action == 'confirmchange') return isset($case->caseStatus) && isset($case->caseVersion) && $case->caseStatus != 'wait' && $case->version < $case->caseVersion;
if($action == 'confirmstorychange') return !empty($case->needconfirm) || (isset($case->browseType) && $case->browseType == 'needconfirm');
if($action == 'createbug') return isset($case->caseFails) && $case->caseFails > 0;
if($action == 'create') return !$case->lib || !empty($case->product);
if($action == 'review') return ($config->testcase->needReview || !empty($config->testcase->forceReview)) && (isset($case->caseStatus) ? $case->caseStatus == 'wait' : $case->status == 'wait');
if($action == 'showscript') return $case->auto == 'auto';
if($action == 'createcase') return !isset($case->lib) || ($case->lib && empty($case->product));
/* 判断确认撤销操作按钮的权限。 */
/* Check confirmdemandretract priv. */
if($action == 'confirmdemandretract') return !empty($case->confirmeActionType) && $case->confirmeActionType == 'confirmedretract';
/* 判断确认移除操作按钮的权限。 */
/* Check confirmdemandunlink priv. */
if($action == 'confirmdemandunlink') return !empty($case->confirmeActionType) && $case->confirmeActionType == 'confirmedunlink';
return true;
}
/**
* 获取导入的字段。
* Get fields for import.
*
* @param int $productID
* @access public
* @return array
*/
public function getImportFields(int $productID = 0): array
{
/* If type of the product isn't normal, set language item of branch. */
/* 如果产品是多分支产品,设置分支语言项。 */
$product = $this->loadModel('product')->getById($productID);
if($product && $product->type != 'normal') $this->lang->testcase->branch = $this->lang->product->branchName[$product->type];
$caseLang = $this->lang->testcase;
$caseConfig = $this->config->testcase;
$fields = explode(',', $caseConfig->exportFields);
foreach($fields as $key => $fieldName)
{
/* 设置字段的语言项。 */
/* Set language item of the fieldName. */
$fieldName = trim($fieldName);
$fields[$fieldName] = isset($caseLang->{$fieldName}) ? $caseLang->{$fieldName} : $fieldName;
unset($fields[$key]);
}
return $fields;
}
/**
* 导入测试用例到用例库。
* Import cases to lib.
*
* @param array $cases
* @param array $steps
* @param array $files
* @access public
* @return bool
*/
public function importToLib(array $cases, array $steps, array $files): bool
{
$this->loadModel('action');
$caseIdList = array_column($cases, 'id');
$oldCaseList = $this->getByList($caseIdList);
foreach($cases as $case)
{
/* 如果用例没有 ID插入用例。 */
/* If case id is not exist, insert it. */
if(!isset($case->id))
{
$this->testcaseTao->doCreate($case);
if(!dao::isError())
{
$caseID = $this->dao->lastInsertID();
$this->action->create('case', $caseID, 'tolib', '', $case->fromCaseID);
}
}
/* 如果用例有 ID更新用例。 */
/* If case id is exist, update it. */
else
{
$caseID = $case->id;
$oldCase = isset($oldCaseList[$caseID]) ? $oldCaseList[$caseID] : null;
$this->testcaseTao->doUpdate($case);
$this->action->create('case', $caseID, 'updatetolib', '', $case->fromCaseID);
$this->dao->delete()->from(TABLE_CASESTEP)->where('`case`')->eq($caseID)->exec();
$removeFiles = $this->dao->select('id,pathname')->from(TABLE_FILE)->where('`objectID`')->eq($caseID)->andWhere('objectType')->eq('testcase')->fetchAll('id');
$this->dao->delete()->from(TABLE_FILE)->where('`objectID`')->eq($caseID)->andWhere('objectType')->eq('testcase')->exec();
foreach($removeFiles as $fileID => $file)
{
if(empty($file->pathname)) continue;
$filePath = pathinfo($file->pathname, PATHINFO_BASENAME);
$datePath = substr($file->pathname, 0, 6);
$filePath = $this->app->getAppRoot() . "www/data/upload/{$this->app->company->id}/" . "{$datePath}/" . $filePath;
if(file_exists($filePath)) unlink($filePath);
}
if(!empty($oldCase) && $oldCase->lib && empty($oldCase->product))
{
$fromcaseVersion = $this->dao->select('fromCaseVersion')->from(TABLE_CASE)->where('fromCaseID')->eq($case->id)->fetch('fromCaseVersion');
$fromcaseVersion = (int)$fromcaseVersion + 1;
$this->dao->update(TABLE_CASE)->set('`fromCaseVersion`')->eq($fromcaseVersion)->where('`fromCaseID`')->eq($case->id)->exec();
}
}
if(isset($caseID))
{
$this->testcaseTao->importSteps($caseID, zget($steps, $case->fromCaseID, array()));
$this->testcaseTao->importFiles($caseID, zget($files, $case->fromCaseID, array()));
}
}
return !dao::isError();
}
/**
* 导入用例关联的模块。
* Import case related modules.
*
* @param int $libID
* @param int $oldModuleID
* @param int $maxOrder
* @access public
* @return int
*/
public function importCaseRelatedModules(int $libID, int $oldModuleID = 0, int $maxOrder = 0): int
{
/* If module has been imported, return imported module id. */
$moduleID = $this->checkModuleImported($libID, $oldModuleID);
if($moduleID) return $moduleID;
/* Build old module, and insert it. */
$oldModule = $this->dao->select('name, parent, grade, `order`, short')->from(TABLE_MODULE)->where('id')->eq($oldModuleID)->fetch();
$oldModule->root = $libID;
$oldModule->from = $oldModuleID;
$oldModule->type = 'caselib';
if(!empty($maxOrder)) $oldModule->order = $maxOrder + $oldModule->order;
$this->dao->insert(TABLE_MODULE)->data($oldModule)->autoCheck()->exec();
if(!dao::isError())
{
/* Get new module id. */
$newModuleID = $this->dao->lastInsertID();
/* Set path and parent. */
if($oldModule->parent)
{
/* If old module has parent module, import parent module. */
$parentModuleID = $this->importCaseRelatedModules($libID, $oldModule->parent, !empty($maxOrder) ? $maxOrder : 0);
$parentModule = $this->dao->select('id, path')->from(TABLE_MODULE)->where('id')->eq($parentModuleID)->fetch();
$parent = $parentModule->id;
$path = $parentModule->path . "$newModuleID,";
}
else
{
$path = ",$newModuleID,";
$parent = 0;
}
/* Update path and parent. */
$this->dao->update(TABLE_MODULE)->set('parent')->eq($parent)->set('path')->eq($path)->where('id')->eq($newModuleID)->exec();
/* Return new module id. */
return $newModuleID;
}
return 0;
}
/**
* 检查模块是否可以导入。
* Adjust module is can import.
*
* @param int $libID
* @param int $oldModule
* @access public
* @return int
*/
public function checkModuleImported(int $libID, int $oldModule = 0): int
{
/* Get module if from is oldModule and root is libID. */
$module = $this->dao->select('id')->from(TABLE_MODULE)
->where('root')->eq($libID)
->andWhere('`from`')->eq($oldModule)
->andWhere('type')->eq('caselib')
->andWhere('deleted')->eq(0)
->fetch();
/* If module isn't exist, return 0. */
if(!$module) return 0;
/* Return module id. */
return $module->id;
}
/**
* 追加 bug 和用例执行结果信息
* Append bugs and results.
*
* @param array $cases
* @param string $type
* @param array $caseIdList
* @access public
* @return void
*/
public function appendData(array $cases, string $type = 'case', array $caseIdList = array()): array
{
if(empty($caseIdList)) $caseIdList = array_keys($cases);
/* 查询用例的 bugs 和结果。 */
/* Get bugs and results. */
$queryField = $type == 'case' ? '`case`' : '`result`';
$caseBugs = $this->dao->select('COUNT(1) AS count, `case`')->from(TABLE_BUG)->where($queryField)->in($caseIdList)->andWhere('deleted')->eq(0)->groupBy('`case`')->fetchPairs('case', 'count');
$results = $this->dao->select('COUNT(1) AS count, `case`')->from(TABLE_TESTRESULT)->where('`case`')->in($caseIdList)->groupBy('`case`')->fetchPairs('case', 'count');
/* 查询用例的失败结果。 */
/* Get result fails of the the testcases. */
if($type != 'case') $queryField = '`run`';
$caseFails = $this->dao->select('COUNT(1) AS count, `case`')->from(TABLE_TESTRESULT)
->where('caseResult')->eq('fail')
->andWhere($queryField)->in($caseIdList)
->groupBy('`case`')
->fetchPairs('case','count');
/* 查询用例的步骤。 */
/* Get the the testcase steps. */
$queryTable = $type == 'case' ? TABLE_CASE : TABLE_TESTRUN;
$queryOn = $type == 'case' ? 't1.`case`=t2.`id`' : 't1.`case`=t2.`case`';
$queryField = $type == 'case' ? 't1.`case`' : 't2.`id`';
$steps = $this->dao->select('COUNT(DISTINCT t1.id) AS count, t1.`case`')->from(TABLE_CASESTEP)->alias('t1')
->leftJoin($queryTable)->alias('t2')->on($queryOn)
->where($queryField)->in($caseIdList)
->andWhere('t1.type')->ne('group')
->andWhere('t1.version=t2.version')
->groupBy('t1.`case`')
->fetchPairs('case', 'count');
/* 设置测试用例的 bugs 执行结果和步骤。 */
/* Set related bugs, results and steps of the testcases. */
foreach($cases as $case)
{
$caseID = $type == 'case' ? $case->id : $case->case;
$case->bugs = zget($caseBugs, $caseID, 0);
$case->results = zget($results, $caseID, 0);
$case->caseFails = zget($caseFails, $caseID, 0);
$case->stepNumber = zget($steps, $caseID, 0);
$case = $this->processDateField($case);
}
return $cases;
}
/**
* 检查是否不需要评审。
* Check whether force not review.
*
* @access public
* @return bool
*/
public function forceNotReview(): bool
{
if(!$this->config->testcase->needReview)
{
if(!isset($this->config->testcase->forceReview)) return true;
if(strpos(",{$this->config->testcase->forceReview},", ",{$this->app->user->account},") === false) return true;
}
elseif(isset($this->config->testcase->forceNotReview) && strpos(",{$this->config->testcase->forceNotReview},", ",{$this->app->user->account},") !== false)
{
return true;
}
return false;
}
/**
* 获取用例总结。
* Summary cases.
*
* @param array $cases
* @access public
* @return string
*/
public function summary(array $cases): string
{
$executed = 0;
foreach($cases as $case)
{
if($case->lastRunResult != '') $executed ++;
}
return sprintf($this->lang->testcase->summary, count($cases), $executed);
}
/**
* 同步用例到项目中。
* Sync case to project.
*
* @param object $case
* @param int $caseID
* @access public
* @return bool
*/
public function syncCase2Project(object $case, int $caseID): bool
{
$projects = array();
if(!empty($case->story))
{
$projects = $this->dao->select('project')->from(TABLE_PROJECTSTORY)->where('story')->eq($case->story)->fetchPairs();
}
elseif($this->app->tab == 'project' && empty($case->story) && empty($case->project))
{
$projects = array($this->session->project);
}
elseif($this->app->tab == 'execution' && empty($case->story) && empty($case->execution))
{
$projects = array($this->session->execution);
}
if(!empty($case->project)) $projects[] = $case->project;
if(!empty($case->execution)) $projects[] = $case->execution;
if(empty($projects)) return false;
$this->loadModel('action');
$projects = array_filter(array_unique($projects));
$objectInfo = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->in($projects)->fetchAll('id', false);
foreach($projects as $projectID)
{
if(!isset($objectInfo[$projectID])) continue;
$lastOrder = (int)$this->dao->select('*')->from(TABLE_PROJECTCASE)->where('project')->eq($projectID)->orderBy('order_desc')->limit(1)->fetch('order');
$lastOrder++;
$data = new stdclass();
$data->project = $projectID;
$data->product = $case->product;
$data->case = $caseID;
$data->version = 1;
$data->order = $lastOrder;
$this->dao->insert(TABLE_PROJECTCASE)->data($data)->exec();
$object = $objectInfo[$projectID];
$objectType = $object->type;
if($objectType == 'project') $this->action->create('case', $caseID, 'linked2project', '', $projectID);
if(in_array($objectType, array('sprint', 'stage')) && $object->multiple) $this->action->create('case', $caseID, 'linked2execution', '', $projectID);
}
return !dao::isError();
}
/**
* processDatas
*
* @param array $datas
* @access public
* @return void
*/
public function processDatas($datas)
{
if(isset($datas->datas)) $datas = $datas->datas;
$stepData = array();
$stepVars = 0;
foreach($datas as $row => $cellValue)
{
foreach($cellValue as $field => $value)
{
if($field != 'stepDesc' and $field != 'stepExpect') continue;
$value = (string)$value;
$steps = $value;
if(strpos($value, "\n"))
{
$steps = explode("\n", $value);
}
elseif(strpos($value, "\r"))
{
$steps = explode("\r", $value);
}
if(is_string($steps)) $steps = explode("\n", $steps);
$stepKey = str_replace('step', '', strtolower($field));
$caseStep = array();
foreach($steps as $step)
{
$trimmedStep = trim($step);
if(empty($trimmedStep)) continue;
preg_match('/^((([0-9]+)[.]([0-9]+))[.]([0-9]+))[.、](.*)$/Uu', $trimmedStep, $out);
if(!$out) preg_match('/^(([0-9]+)[.]([0-9]+))[.、](.*)$/Uu', $trimmedStep, $out);
if(!$out) preg_match('/^([0-9]+)[.、](.*)$/Uu', $trimmedStep, $out);
if($out && !empty(trim($out[1])))
{
$count = count($out);
$num = $out[1];
$parent = $count > 4 ? $out[2] : '0';
$grand = $count > 6 ? $out[3] : '0';
$step = trim($out[2]);
if($count > 4) $step = $count > 6 ? trim($out[6]) : trim($out[4]);
$caseStep[$num]['content'] = $step;
$caseStep[$num]['number'] = $num;
$caseStep[$num]['type'] = $count > 4 ? 'item' : 'step';
if(!empty($parent)) $caseStep[$parent]['type'] = 'group';
if(!empty($grand)) $caseStep[$grand]['type'] = 'group';
}
elseif(isset($num))
{
$caseStep[$num]['content'] = isset($caseStep[$num]['content']) ? "{$caseStep[$num]['content']}\n{$step}" : "\n{$step}";
}
elseif($field == 'stepDesc')
{
$num = 1;
$caseStep[$num]['content'] = $step;
$caseStep[$num]['type'] = 'step';
$caseStep[$num]['number'] = $num;
}
elseif($field == 'stepExpect' && isset($stepData[$row]['desc']))
{
end($stepData[$row]['desc']);
$num = key($stepData[$row]['desc']);
$caseStep[$num]['content'] = $step;
$caseStep[$num]['number'] = $num;
}
}
unset($num);
unset($sign);
if($stepKey == 'expect' && !empty($stepData[$row]['desc']))
{
foreach($stepData[$row]['desc'] as $stepDescValue)
{
if(empty($stepDescValue['number'])) continue;
$caseNumber = $stepDescValue['number'];
if($stepDescValue && !isset($caseStep[$caseNumber]) || empty($caseStep[$caseNumber]['content'])) $caseStep[$caseNumber] = '';
}
}
$stepVars += count($caseStep, COUNT_RECURSIVE) - count($caseStep);
$stepData[$row][$stepKey] = $caseStep;
}
}
return $stepData;
}
/**
* Process date field when date is empty.
*
* @param object $case
* @access public
* @return object
*/
public function processDateField(object $case): object
{
foreach($case as $key => $value)
{
if(strpos($key, 'Date') !== false && helper::isZeroDate($value)) $case->$key = '';
}
return $case;
}
/**
* 处理批量表单内的用例步骤或预期。
* Process steps or expects in batch form.
*
* @param array $steps
* @access protected
* @return array
*/
public function processStepsOrExpects(string $steps): array
{
$caseSteps = array();
$stepTypes = array();
$steps = explode("\n", trim($steps));
$preParent = $preGrand = $preNum = '0';
foreach($steps as $step)
{
$step = trim($step);
if(empty($step)) continue;
$appendToPre = false;
preg_match('/^((([0-9]+)[.]([0-9]+)[.]([0-9]+))[.]([0-9]+))[.、](.*)$/Uu', $step, $out);
if($out) $appendToPre = true; // 如果层级大于3级追加到目前的步骤中
preg_match('/^((([0-9]+)[.]([0-9]+))[.]([0-9]+))[.、](.*)$/Uu', $step, $out);
if(!$out) preg_match('/^(([0-9]+)[.]([0-9]+))[.、](.*)$/Uu', $step, $out);
if(!$out) preg_match('/^([0-9]+)[.、](.*)$/Uu', $step, $out);
$grand = $parent = $num = '0';
if(!$appendToPre && $out && isset($caseSteps[$out[1]]))
{
$appendToPre = true; // 如果已经设置过,追加到目前的步骤中
}
elseif(!$appendToPre && $out)
{
$count = count($out);
if($count > 6)
{
$grand = $out[3];
$parent = $out[4];
$num = $out[5];
}
elseif($count > 4)
{
$grand = $out[2];
$parent = $out[3];
}
else
{
$grand = $out[1];
}
$appendToPre = !(($grand == $preGrand && $parent == $preParent && $num == $preNum + 1)
|| ($grand == $preGrand && $parent == $preParent + 1 && $num == '0')
|| ($grand == $preGrand + 1 && $parent == '0' && $num == '0'));
}
if(!$appendToPre && $out)
{
$preGrand = $grand;
$preParent = $parent;
$preNum = $num;
$parent = $count > 4 ? $out[2] : '0';
$step = trim($out[2]);
$code = $out[1];
if($count > 4) $step = $count > 6 ? trim($out[6]) : trim($out[4]);
$caseSteps[$code] = $step;
$stepTypes[$code] = $count > 4 ? 'item' : 'step';
if($count > 4 && !empty($parent)) $stepTypes[$parent] = 'group';
if($count > 6 && !empty($grand)) $stepTypes[$grand] = 'group';
}
elseif($appendToPre && isset($code))
{
$caseSteps[$code] = isset($caseSteps[$code]) ? "{$caseSteps[$code]}\n{$step}" : "\n{$step}";
}
elseif(!$out && isset($code))
{
$caseSteps[$code] = isset($caseSteps[$code]) ? "{$caseSteps[$code]}\n{$step}" : "\n{$step}";
}
}
if(empty($caseSteps) && !empty($steps))
{
$caseSteps[] = implode("\n", $steps);
$stepTypes[] = 'step';
}
return array($caseSteps, $stepTypes);
}
/**
* 判断步骤是否变更。
* Judge if steps changed.
*
* @param object $case
* @param array $oldStep
* @access protected
* @return bool
*/
protected function processStepsChanged(object $case, array $oldStep): bool
{
$newSteps = array_filter($case->steps);
$stepChanged = (count($oldStep) != count($newSteps));
if(!$stepChanged)
{
$desc = array_values($case->steps);
$expect = array_values($case->expects);
$stepType = array_values($case->stepType);
foreach($oldStep as $index => $step)
{
if($stepChanged) break;
if(!isset($desc[$index]) || !isset($expect[$index]) || $step->desc != $desc[$index] || $step->expect != $expect[$index] || $step->type != $stepType[$index]) $stepChanged = true;
}
}
return $stepChanged;
}
/**
* 为 datatable 获取模块。
* Get modules for datatable.
*
* @param int $productID
* @access public
* @return void
*/
public function getDatatableModules(int $productID): array
{
$branches = $this->loadModel('branch')->getPairs($productID);
$modules = $this->loadModel('tree')->getOptionMenu($productID, 'case', 0);
if(count($branches) <= 1) return $modules;
foreach($branches as $branchID => $branchName) $modules += $this->tree->getOptionMenu($productID, 'case', 0, (string)$branchID);
return $modules;
}
/**
* Batch change scene.
*
* @param array $caseIdList
* @param int $sceneID
* @access public
* @return bool
*/
public function batchChangeScene(array $caseIdList, int $sceneID): bool
{
if($sceneID < 0 || $sceneID > 16777215) return false; // The scene column's data type is mediumint unsigned and its range is 0-16777215.
$caseIdList = array_filter($caseIdList);
if(!$caseIdList) return false;
$oldCases = $this->getByList($caseIdList, "scene != '{$sceneID}'");
if(!$oldCases) return false;
$case = new stdclass();
$case->scene = $sceneID;
$case->lastEditedBy = $this->app->user->account;
$case->lastEditedDate = helper::now();
$this->dao->update(TABLE_CASE)->data($case)->where('id')->in(array_keys($oldCases))->exec();
if(dao::isError()) return false;
$this->loadModel('action');
foreach($oldCases as $oldCase)
{
$changes = common::createChanges($oldCase, $case);
if($changes)
{
$actionID = $this->action->create('case', $oldCase->id, 'edited');
$this->action->logHistory($actionID, $changes);
}
}
return !dao::isError();
}
/**
* 为构建场景菜单获取场景。
* Get scenes for menu.
*
* @param int $productID
* @param int $moduleID
* @param int $startScene
* @param string $branch
* @param int $currentScene
* @access public
* @return array
*/
public function getScenesForMenu(int $productID, int $moduleID, int $startScene = 0, string $branch = 'all', int $currentScene = 0): array
{
/* Set the start scene. */
$startScenePath = '';
if($startScene > 0)
{
$startScene = $this->getSceneByID($startScene);
if($startScene) $startScenePath = $startScene->path . '%';
}
$currentScenePath = '';
if($currentScene > 0)
{
$currentScene = $this->getSceneByID($currentScene);
if($currentScene) $currentScenePath = $currentScene->path . '%';
}
/* Return scenes. */
return $this->dao->select('*')->from(TABLE_SCENE)
->where('deleted')->eq(0)
->beginIF($productID)->andWhere('product')->eq($productID)->fi()
->beginIF($moduleID > 0)->andWhere('module')->eq($moduleID)->fi()
->beginIF($startScenePath)->andWhere('path')->like($startScenePath)->fi()
->beginIF($currentScenePath)->andWhere('path')->notlike($currentScenePath)->fi()
->beginIF($branch !== 'all' && $branch !== '')->andWhere('branch')->eq((int)$branch)->fi()
->orderBy('grade desc, sort')
->fetchAll('id', false);
}
/**
* 构建树数组。
* Build tree array.
*
* @param array $treeMenu
* @param array $scenes
* @param object $scene
* @param string $sceneName
* @access public
* @return void
*/
public function buildTreeArray(array &$treeMenu, array $scenes, object $scene, string $sceneName = '/'): void
{
$parentScenes = explode(',', $scene->path);
foreach($parentScenes as $parentSceneID)
{
if(empty($parentSceneID)) continue;
if(empty($scenes[$parentSceneID])) continue;
$sceneName .= $scenes[$parentSceneID]->title . '/';
}
$sceneName = rtrim($sceneName, '/');
$sceneName .= "|$scene->id\n";
if(isset($treeMenu[$scene->id]) and !empty($treeMenu[$scene->id]))
{
if(isset($treeMenu[$scene->parent]))
{
$treeMenu[$scene->parent] .= $sceneName;
}
else
{
$treeMenu[$scene->parent] = $sceneName;
}
$treeMenu[$scene->parent] .= $treeMenu[$scene->id];
}
else
{
if(isset($treeMenu[$scene->parent]) and !empty($treeMenu[$scene->parent]))
{
$treeMenu[$scene->parent] .= $sceneName;
}
else
{
$treeMenu[$scene->parent] = $sceneName;
}
}
}
/**
* 根据 ID 获取一个场景。
* Get a scene by id.
*
* @param int $sceneID
* @access public
* @return object
*/
public function getSceneByID(int $sceneID): object|bool
{
return $this->dao->select('*')->from(TABLE_SCENE)->where('id')->eq($sceneID)->fetch();
}
/**
* 创建一个场景。
* Create a scene.
*
* @access public
* @return bool
*/
public function createScene(object $scene): bool
{
$product = zget($scene, 'product', 0);
$this->dao->insert(TABLE_SCENE)->data($scene)
->autoCheck()
->batchCheck($this->config->testcase->createscene->requiredFields, 'notempty')
->checkIF($product, 'title', 'unique', "product = '{$product}'")
->checkFlow()
->exec();
if(dao::isError()) return false;
$sceneID = $this->dao->lastInsertID();
$scene->sort = $sceneID;
$scene->path = ',' . $sceneID . ',';
$scene->grade = 1;
if(!empty($scene->parent))
{
$parent = $this->getSceneByID($scene->parent);
if($parent)
{
$scene->path = $parent->path . $sceneID . ',';
$scene->grade = ++$parent->grade;
$scene->product = $parent->product;
$scene->branch = $parent->branch;
$scene->module = $parent->module;
}
else
{
$scene->parent = 0;
}
}
$this->dao->update(TABLE_SCENE)->data($scene)->where('id')->eq($sceneID)->exec();
if(dao::isError()) return false;
$this->loadModel('action')->create('scene', $sceneID, 'Opened');
return !dao::isError();
}
/**
* 获取所有的子场景 id。
* Get all children id.
*
* @param int $sceneID
* @access public
* @return array
*/
public function getAllChildId(int $sceneID): array
{
if($sceneID == 0) return array();
$scene = $this->dao->findById($sceneID)->from(TABLE_SCENE)->fetch();
if(empty($scene)) return array();
return $this->dao->select('id')->from(TABLE_SCENE)
->where('path')->like($scene->path . '%')
->andWhere('deleted')->eq(0)
->fetchPairs();
}
/**
* 通过 ID 列表和查询语句获取场景。
* Get scenes by id list and query string.
*
* @param array $sceneIdList
* @param string $query
* @access public
* @return array
*/
public function getScenesByList(array $sceneIdList, string $query = ''): array
{
if(!$sceneIdList) return array();
return $this->dao->select('*')->from(TABLE_SCENE)
->where('deleted')->eq('0')
->andWhere('id')->in($sceneIdList)
->beginIF($query)->andWhere($query)->fi()
->fetchAll('id', false);
}
/**
* 获取包含子场景的场景列表。
* Get scene list include sub scenes.
*
* @param int|array $productID
* @param string $branch
* @param int $moduleID
* @param string $orderBy
* @param object $pager
* @access public
* @return array
*/
public function getSceneGroups(int|array $productID, string $branch = '', int $moduleID = 0, string $orderBy = 'id_desc', ?object $pager = null): array
{
$modules = [];
if($moduleID)
{
$modules = $this->loadModel('tree')->getAllChildId($moduleID);
if(!$modules) return [];
}
$topScenes = $this->dao->select('*')->from(TABLE_SCENE)
->where('deleted')->eq('0')
->andWhere('grade')->eq(1)
->andWhere('product')->in($productID)
->beginIF($branch !== 'all')->andWhere('branch')->eq($branch)->fi()
->beginIF($modules)->andWhere('module')->in($modules)->fi()
->orderBy($orderBy)
->page($pager)
->fetchAll('id', false);
if(!$topScenes) return array();
$subScenes = $this->dao->select('*')->from(TABLE_SCENE)
->where('deleted')->eq('0')
->andWhere('grade')->gt(1)
->andWhere('product')->in($productID)
->beginIF($branch !== 'all')->andWhere('branch')->eq($branch)->fi()
->beginIF($modules)->andWhere('module')->in($modules)->fi()
->orderBy($orderBy)
->fetchAll('id', false);
foreach($subScenes as $id => $scene)
{
$path = trim($scene->path, ',');
$root = substr($path, 0, strpos($path, ','));
if(!isset($topScenes[$root])) unset($subScenes[$id]);
}
return $subScenes + $topScenes;
}
/**
* 获取用场景 ID 分组的用例。
* Get cases by scene id.
*
* @param int|array $productID
* @param string $branch
* @param array $modules
* @param string $caseType
* @param string $orderBy
* @access public
* @return array
*/
public function getSceneGroupCases(int|array $productID, string $branch, array $modules, string $caseType, string $orderBy): array
{
$browseType = $this->session->caseBrowseType && $this->session->caseBrowseType != 'bysearch' ? $this->session->caseBrowseType : 'all';
$stmt = $this->dao->select('t1.*,t3.title as storyTitle')->from(TABLE_CASE)->alias('t1');
if($this->app->tab == 'project') $stmt = $stmt->leftJoin(TABLE_PROJECTCASE)->alias('t2')->on('t1.id=t2.case');
$stmt->leftJoin(TABLE_STORY)->alias('t3')->on('t1.story = t3.id');
$caseList = $stmt->where('t1.deleted')->eq('0')
->andWhere('t1.scene')->ne(0)
->andWhere('t1.product')->in($productID)
->beginIF($this->app->tab == 'project')->andWhere('t2.project')->eq($this->session->project)->fi()
->beginIF($branch !== 'all')->andWhere('t1.branch')->eq($branch)->fi()
->beginIF($modules)->andWhere('t1.module')->in($modules)->fi()
->beginIF($browseType == 'wait')->andWhere('t1.status')->eq($browseType)->fi()
->beginIF($this->cookie->onlyAutoCase)->andWhere('t1.auto')->eq('auto')->fi()
->beginIF(!$this->cookie->onlyAutoCase)->andWhere('t1.auto')->ne('unit')->fi()
->beginIF($caseType)->andWhere('t1.type')->eq($caseType)->fi()
->orderBy($orderBy)
->fetchAll('id', false);
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'testcase', false);
$caseList = $this->loadModel('story')->checkNeedConfirm($caseList);
$caseList = $this->appendData($caseList);
$cases = array();
foreach($caseList as $case) $cases[$case->scene][$case->id] = $case;
return $cases;
}
/**
* 获取场景菜单。
* Get scene menu.
*
* @param int $productID
* @param int $moduleID
* @param string $branch
* @param int $startScene
* @param int $currentScene
* @param bool $emptyMenu
* @access public
* @return array
*/
public function getSceneMenu(int $productID, int $moduleID = 0, string $branch = 'all', int $startScene = 0, int $currentScene = 0, bool $emptyMenu = false): array
{
$branches = array($branch => '');
if($branch != 'all')
{
$product = $this->loadModel('product')->getById($productID);
if($product && $product->type != 'normal')
{
$branchPairs = $this->loadModel('branch')->getPairs($productID, 'all');
$branches = array($branch => $branchPairs[$branch]);
}
elseif($product && $product->type == 'normal')
{
$branches = array('0' => '');
}
}
$treeMenu = array();
foreach($branches as $branchID => $branch)
{
$scenes = $this->getScenesForMenu($productID, $moduleID, $startScene, (string)$branchID, $currentScene);
foreach($scenes as $scene)
{
$branchName = !empty($product) && $product->type != 'normal' && $scene->branch === BRANCH_MAIN ? $this->lang->branch->main : $branch;
$this->buildTreeArray($treeMenu, $scenes, $scene, (empty($branchName)) ? '/' : "/$branchName/");
}
}
ksort($treeMenu);
$topMenu = @array_shift($treeMenu);
$topMenu = explode("\n", trim((string)$topMenu));
$lastMenu[] = '/';
foreach($topMenu as $menu)
{
if(!strpos($menu, '|')) continue;
list($label, $sceneID) = explode('|', $menu);
$lastMenu[$sceneID] = $label;
}
/* Attach empty option. */
if($emptyMenu) $lastMenu['null'] = $this->lang->null;
return $lastMenu;
}
/**
* 获取场景的名称。
* Get scene name.
*
* @param array $sceneIDList
* @param bool $fullPath
* @access public
* @return array
*/
public function getScenesName(array $sceneIDList, bool $fullPath = true): array
{
if(!$fullPath) return $this->dao->select('id, title')->from(TABLE_SCENE)->where('deleted')->eq('0')->andWhere('id')->in($sceneIDList)->fetchPairs();
$scenes = $this->dao->select('id, title, path')->from(TABLE_SCENE)->where('deleted')->eq('0')->andWhere('id')->in($sceneIDList)->fetchAll('path');
$allScenes = $this->dao->select('id, title')->from(TABLE_SCENE)->where('deleted')->eq('0')->andWhere('id')->in(implode(',', array_keys($scenes)))->fetchPairs();
$scenePairs = array();
foreach($scenes as $scene)
{
$title = '';
$path = explode(',', trim($scene->path, ','));
foreach($path as $sceneID) $title .= '/' . $allScenes[$sceneID];
$scenePairs[$scene->id] = $title;
}
return $scenePairs;
}
/**
* 编辑一个场景和它的子场景。
* Update a scene and its children.
*
* @param object $scene
* @access public
* @return bool
*/
public function updateScene(object $scene, object $oldScene): bool
{
$this->dao->update(TABLE_SCENE)->data($scene)
->autoCheck()
->batchCheck($this->config->testcase->editscene->requiredFields, 'notempty')
->check('title', 'unique', "product = '{$scene->product}' AND id != '{$oldScene->id}'")
->where('id')->eq($oldScene->id)
->checkFlow()
->exec();
if(dao::isError()) return false;
if(isset($scene->parent) && $scene->parent != $oldScene->parent)
{
if($scene->parent)
{
$parent = $this->getSceneByID($scene->parent);
$scene->path = $parent->path . $oldScene->id . ',';
$scene->grade = ++$parent->grade;
$scene->product = $parent->product;
$scene->branch = $parent->branch;
$scene->module = $parent->module;
}
else
{
$scene->path = ',' . $oldScene->id . ',';
$scene->grade = 1;
}
$this->dao->update(TABLE_SCENE)->data($scene)->where('id')->eq($oldScene->id)->exec();
$this->dao->update(TABLE_SCENE)->set("path = REPLACE(path, '{$oldScene->path}', '{$scene->path}')")
->where('id')->ne($oldScene->id)
->andWhere('path')->like("{$oldScene->path}%")
->exec();
}
$changes = common::createChanges($oldScene, $scene);
if($changes)
{
$actionID = $this->loadModel('action')->create('scene', $oldScene->id, 'edited');
$this->action->logHistory($actionID, $changes);
}
return true;
}
/**
* 获取 xmind 文件内容。
* Get xmind file content.
*
* @param string $fileName
* @access public
* @return string
*/
public function getXmindImport(string $fileName): string
{
$xmlNode = simplexml_load_file($fileName);
$testData = $this->xmlToArray($xmlNode);
return json_encode($testData);
}
/**
* 保存 xmind 文件内容。
* Save xmind file content to database.
*
* @param array $scenes
* @param array $testcases
* @access public
* @return array
*/
public function saveXmindImport(array $scenes, array $testcases): array
{
try
{
$this->dao->begin();
$sceneList = array_combine(array_map(function($scene){return $scene['tmpId'];}, $scenes), array_map(function($scene){return (array)$scene;}, $scenes));
$sceneIDList = array();
foreach($sceneList as $key => &$scene)
{
$result = $this->testcaseTao->saveScene($scene, $sceneList);
if($result['result'] == 'fail') throw new Exception($result['message']);
$scene['id'] = $result['sceneID'];
$sceneIDList[$key]['id'] = $result['sceneID'];
}
foreach($testcases as $testcase)
{
$result = $this->saveTestcase($testcase, $sceneIDList);
if($result['result'] == 'fail') throw new Exception($result['message']);
}
$this->dao->commit();
return array('result' => 'success', 'message' => $this->lang->saveSuccess);
}
catch (Exception $e)
{
$this->dao->rollBack();
return array('result' => 'fail', 'message' => $e->getMessage());
}
}
/**
* 导入 xmind 时保存其中的测试用例。
* Save the test cases in xmind when importing it.
*
* @param object $testcase
* @param array $sceneIdList
* @access public
* @return array
*/
public function saveTestcase(object $testcase, array $sceneIdList): array
{
$scene = 0;
$nodeID = $testcase->tmpPId;
if(isset($sceneIdList[$nodeID]['id'])) $scene = $sceneIdList[$nodeID]['id'];
unset($testcase->tmpPId);
$testcase->scene = $scene;
if(empty($testcase->id))
{
$this->create($testcase);
}
else
{
$oldCase = $this->getById($testcase->id);
$changes = $this->update($testcase, $oldCase);
if($changes)
{
$actionID = $this->loadModel('action')->create('case', $testcase->id, 'edited');
$this->action->logHistory($actionID, $changes);
}
}
if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError(true));
return array('result' => 'success', 'message' => 1, 'testcaseID' => zget($testcase, 'id', 0));
}
/**
* 把导入 xmind 时 POST 提交的 stepList 属性转换为创建用例和编辑用例表单提交的 steps、expects 和 stepType 属性。
* Convert the stepList attribute submitted by POST when importing xmind into the steps, expects, and stepType attributes submitted by the form for creating and editing use cases.
*
* @param object $case
* @param object $testcase
* @access protected
* @return object
*/
protected function processCaseSteps(object $case, object $testcase): object
{
$case->steps = array();
$case->expects = array();
$case->stepType = array();
if(isset($testcase->stepList))
{
$index = 1;
$postSteps = $testcase->stepList;
$stepList = array_combine(array_map(function($step){return $step['tmpId'];}, $postSteps), array_map(function($step){return (object)$step;}, $postSteps));
foreach($stepList as $step)
{
if(isset($stepList[$step->tmpPId]))
{
$parentStep = $stepList[$step->tmpPId];
$parentStep->children = isset($parentStep->children) ? $parentStep->children : array();
$step->index = $parentStep->index . '.' . (count($parentStep->children) + 1);
$parentStep->children[] = $step;
}
else
{
$step->index = $index;
$step->children = array();
$index++;
}
$case->steps[$step->index] = zget($step, 'desc', '');
$case->expects[$step->index] = zget($step, 'expect', '');
$case->stepType[$step->index] = zget($step, 'type', 'item');
}
}
return $case;
}
/**
* 导出 xmind 格式的用例时获取用例列表。
* Get case list for export xmind.
*
* @param int $productID
* @param int $moduleID
* @param string $branch
* @access public
* @return array
*/
public function getCaseListForXmindExport(int $productID, int $moduleID, string $branch = ''): array
{
$fields = 't1.id AS testcaseID, t1.title AS `name`, t1.pri, t2.id AS productID, t2.`name` AS productName, t3.id AS moduleID, t3.`name` AS moduleName, t4.id AS sceneID, t4.title AS sceneName, t1.precondition';
return $this->dao->select($fields)->from(TABLE_CASE)->alias('t1')
->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id')
->leftJoin(TABLE_MODULE)->alias('t3')->on('t1.module = t3.id')
->leftJoin(TABLE_SCENE)->alias('t4')->on('t1.scene = t4.id')
->where('t1.deleted')->eq('0')
->andWhere('t1.product')->eq($productID)
->beginIF($moduleID)->andWhere('t1.module')->eq($moduleID)->fi()
->beginIF($branch !== '' && $branch !== 'all')->andWhere('t1.branch')->eq($branch)->fi()
->fetchAll();
}
/**
* 通过产品和模块获取步骤信息。
* Get step by product and module.
*
* @param int $productID
* @param int $moduleID
* @access public
* @return array
*/
public function getStepByProductAndModule(int $productID, int $moduleID): array
{
$fields = "t1.id as testcaseID,"
. "t2.id as stepID,"
. "t2.type,"
. "t2.parent as parentID,"
. "t2.`desc`,"
. "t2.expect";
$stepList = $this->dao->select($fields)->from(TABLE_CASE)->alias('t1')
->leftJoin(TABLE_CASESTEP)->alias('t2')->on('t1.id = t2.`case` and t1.version = t2.version')
->where('t1.deleted')->eq(0)
->andWhere('t1.product')->eq($productID)
->andWhere('t2.id')->gt('0')
->beginIF($moduleID > 0)->andWhere('t1.module')->eq($moduleID)->fi()
->fetchAll();
return $stepList;
}
/**
* 分组获取用例的步骤信息。
* Get steps grouped by case id.
*
* @param array $caseID
* @param string $status
* @access public
* @return array
*/
public function getStepGroupByIdList(array $caseIdList, $status = ''): array
{
if(!$caseIdList) return array();
return $this->dao->select('t1.*')->from(TABLE_CASESTEP)->alias('t1')
->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case=t2.id')
->where('t2.id')->in($caseIdList)
->andWhere('t1.version=t2.version')
->beginIF($status != 'all')->andWhere('t2.status')->ne('wait')->fi()
->fetchGroup('case', 'id');
}
/**
* 通过产品和模块获取场景。
* Get scene by product and module.
*
* @param int $productID
* @param int $moduleID
* @access public
* @return array
*/
public function getSceneByProductAndModule($productID, $moduleID): array
{
/* Get scenes by product and module. */
$sceneList = $this->dao->select('id as sceneID, title as sceneName, path, parent as parentID, product as productID, module as moduleID')
->from(TABLE_SCENE)
->where('deleted')->eq(0)
->andWhere('product')->eq($productID)
->beginIF($moduleID > 0)->andWhere('module')->eq($moduleID)->fi()
->fetchAll();
$sceneMaps = array();
$topScenes = array();
foreach($sceneList as $one)
{
/* If scene parent is zero, append it to topScenes. */
if($one->parentID == 0) $topScenes[] = $one;
/* Append it to sceneMaps. */
$sceneMaps[$one->sceneID] = $one;
}
return array('sceneMaps' => $sceneMaps,'topScenes' => $topScenes);
}
/**
* 存储 mind 配置。
* Save mind config.
*
* @param array $configList
* @access public
* @return array
*/
public function saveMindConfig(string $type, array $configList): array
{
$this->dao->begin();
$this->dao->delete()->from(TABLE_CONFIG)
->where('owner')->eq($this->app->user->account)
->andWhere('module')->eq('testcase')
->andWhere('section')->eq($type)
->exec();
foreach($configList as $one)
{
$config = new stdclass();
$config->module = 'testcase';
$config->section = $type;
$config->key = $one['key'];
$config->value = $one['value'];
$config->owner = $this->app->user->account;
$this->dao->insert(TABLE_CONFIG)->data($config)->autoCheck()->exec();
if($this->dao->isError())
{
$this->dao->rollBack();
return array('result' => 'fail', 'message' => $this->dao->getError(true));
}
}
$this->dao->commit();
return array("result" => "success", "message" => 1);
}
/**
* 获取 xmind 配置。
* Get xmind config.
*
* @access public
* @return array
*/
public function getMindConfig(string $type): array
{
$configItems = $this->dao->select("`key`,value")->from(TABLE_CONFIG)
->where('owner')->eq($this->app->user->account)
->andWhere('module')->eq('testcase')
->andWhere('section')->eq($type)
->fetchAll();
$config = array();
foreach($configItems as $item) $config[$item->key] = $item->value;
if(!isset($config['module'])) $config['module'] = 'M';
if(!isset($config['scene'])) $config['scene'] = 'S';
if(!isset($config['case'])) $config['case'] = 'C';
if(!isset($config['precondition'])) $config['precondition'] = 'pre';
if(!isset($config['pri'])) $config['pri'] = 'P';
if(!isset($config['group'])) $config['group'] = 'G';
return $config;
}
/**
* 将 xml 内容转为数组。
* Convert xml to array.
*
* @param object $xml
* @param array $options
* @access private
* @return array
*/
private function xmlToArray(object $xml, array $options = array()): array
{
$defaults = array(
'namespaceRecursive' => false, // Get XML doc namespaces recursively
'removeNamespace' => true, // Remove namespace from resulting keys
'namespaceSeparator' => ':', // Change separator to something other than a colon
'attributePrefix' => '', // Distinguish between attributes and nodes with the same name
'alwaysArray' => array(), // Array of XML tag names which should always become arrays
'autoArray' => true, // Create arrays for tags which appear more than once
'textContent' => 'text', // Key used for the text content of elements
'autoText' => true, // Skip textContent key if node has no attributes or child nodes
'keySearch' => false, // (Optional) search and replace on tag and attribute names
'keyReplace' => false, // (Optional) replace values for above search values
);
$options = array_merge($defaults, $options);
$namespaces = $xml->getDocNamespaces($options['namespaceRecursive']);
$namespaces[''] = null; // Add empty base namespace
/* Get attributes from all namespaces. */
$attributesArray = array();
foreach($namespaces as $prefix => $namespace)
{
if($options['removeNamespace']) $prefix = '';
foreach($xml->attributes($namespace) as $attributeName => $attribute)
{
// (Optional) replace characters in attribute name
if($options['keySearch']) $attributeName = str_replace($options['keySearch'], $options['keyReplace'], $attributeName);
$attributeKey = $options['attributePrefix'] . ($prefix ? $prefix . $options['namespaceSeparator'] : '') . $attributeName;
$attributesArray[$attributeKey] = (string) $attribute;
}
}
// Get child nodes from all namespaces
$tagsArray = $this->getXmlTagsArray($xml, $namespaces, $options);
// Get text content of node
$textContentArray = array();
$plainText = trim((string) $xml);
if($plainText !== '') $textContentArray[$options['textContent']] = $plainText;
// Stick it all together
$propertiesArray = !$options['autoText'] || $attributesArray || $tagsArray || $plainText === '' ? array_merge($attributesArray, $tagsArray, $textContentArray) : $plainText;
// Return node as array
return array($xml->getName() => $propertiesArray);
}
/**
* 获取 xml 标签数组。
* Get xml tags array.
*
* @param object $xml
* @param array $namespaces
* @param array $options
* @access public
* @return array
*/
public function getXmlTagsArray(object $xml, array $namespaces, array $options): array
{
$tagsArray = array();
foreach($namespaces as $prefix => $namespace)
{
if($options['removeNamespace']) $prefix = '';
foreach($xml->children($namespace) as $childXml)
{
// Recurse into child nodes
$childArray = $this->xmlToArray($childXml, $options);
$childTagName = key($childArray);
$childProperties = current($childArray);
// Replace characters in tag name
if($options['keySearch']) $childTagName = str_replace($options['keySearch'], $options['keyReplace'], $childTagName);
// Add namespace prefix, if any
if($prefix) $childTagName = $prefix . $options['namespaceSeparator'] . $childTagName;
if(!isset($tagsArray[$childTagName]))
{
// Only entry with this key
// Test if tags of this type should always be arrays, no matter the element count
$tagsArray[$childTagName] = in_array($childTagName, $options['alwaysArray'], true) || !$options['autoArray'] ? array($childProperties) : $childProperties;
}
elseif(is_array($tagsArray[$childTagName]) && array_keys($tagsArray[$childTagName]) === range(0, count($tagsArray[$childTagName]) - 1))
{
// Key already exists and is integer indexed array
$tagsArray[$childTagName][] = $childProperties;
}
else
{
// Key exists so convert to integer indexed array with previous value in position 0
$tagsArray[$childTagName] = array($tagsArray[$childTagName], $childProperties);
}
}
}
return $tagsArray;
}
/**
* 追加用例执行失败次数。
* Append case fails.
*
* @param object $case
* @param string $from
* @param int $taskID
* @access public
* @return object
*/
public function appendCaseFails(object $case, string $from, int $taskID): object
{
$caseFails = $this->dao->select('COUNT(1) AS count')->from(TABLE_TESTRESULT)
->where('caseResult')->eq('fail')
->andwhere('`case`')->eq($case->id)
->beginIF($from == 'testtask')->andwhere('`run`')->eq($taskID)->fi()
->fetch('count');
$case->caseFails = $caseFails;
return $case;
}
/**
* 添加步骤。
* Append steps.
*
* @param array $steps
* @param int $count
* @access public
* @return array
*/
public function appendSteps(array $steps, int $count = 0): array
{
if($count == 0) $count = $this->config->testcase->defaultSteps;
if(count($steps) < $count)
{
$step = new stdclass();
$step->step = '';
$step->desc = '';
$step->expect = '';
$step->type = 'step';
for($i = count($steps) + 1; $i <= $count; $i ++)
{
$data = clone $step;
$data->name = (string)$i;
$steps[] = $data;
}
}
return $steps;
}
/**
* 获取可以导入的测试用例。
* Get can import cases.
*
* @param int $productID
* @param int $libID
* @param string $orderBy
* @param object $pager
* @param int $queryID
* @access public
* @return array
*/
public function getCanImportCases(int $productID, int $libID, string $orderBy = 'id_desc', ?object $pager = null, string $browseType = '', int $queryID = 0): array
{
$query = '';
if($browseType == 'bysearch')
{
$queryID = (int)$queryID;
if($queryID)
{
$this->session->set('testsuiteQuery', ' 1 = 1');
$query = $this->loadModel('search')->getQuery($queryID);
if($query)
{
$this->session->set('testsuiteQuery', $query->sql);
$this->session->set('testsuiteForm', $query->form);
}
}
else
{
if($this->session->testsuiteQuery === false) $this->session->set('testsuiteQuery', ' 1 = 1');
}
$query = $this->session->testsuiteQuery;
$allLib = "`lib` = 'all'";
$query = strpos($query, $allLib) !== false ? str_replace($allLib, '1 = 1', $query) : "{$query} AND `lib` = '{$libID}'";
}
$this->loadModel('branch');
$this->loadModel('tree');
$product = $this->loadModel('product')->getById($productID);
$branches = $product->type != 'normal' ? array(BRANCH_MAIN => $this->lang->branch->main) + $this->branch->getPairs($productID, 'active') : array(0);
$branches = array_keys($branches);
$branchModules = array();
foreach($branches as $branch) $branchModules[$branch] = $this->tree->getOptionMenu($productID, 'case', 0, (string)$branch);
$caseModuleCount = $this->dao->select('fromCaseID,count(module) AS moduleCount')
->from(TABLE_CASE)->alias('t1')
->leftJoin(TABLE_MODULE)->alias('t2')->on('t1.module=t2.id and t1.product = t2.root')
->where('t1.product')->eq($productID)
->andWhere('t1.lib')->eq($libID)
->andWhere('t1.fromCaseID')->ne('0')
->andWhere('t1.deleted')->eq('0')
->andWhere('((t2.type')->in('story,case')->andWhere('t2.deleted')->eq('0')->markRight(1)
->orWhere('t1.module')->eq('0')->markRight(1)
->groupBy('t1.fromCaseID')
->fetchPairs();
$branchModuleCount = $this->dao->select('branch,count(id) + 1 as moduleCount')
->from(TABLE_MODULE)
->where('root')->eq($productID)
->andWhere('type')->in('story,case')
->andWhere('deleted')->eq('0')
->groupBy('branch')
->fetchPairs();
foreach($branches as $branch)
{
if(!isset($branchModuleCount[$branch])) $branchModuleCount[$branch] = 0;
if(!empty($branch) && isset($branchModuleCount[0])) $branchModuleCount[$branch] += $branchModuleCount[0];
}
$maxCount = array_sum($branchModuleCount);
$canImport = $canNotImport = array();
$libCases = $this->loadModel('caselib')->getLibCases($libID, 'all');
foreach($libCases as $caseID => $case)
{
$caseModuleCount = zget($caseModuleCount, $caseID, 0);
if(!empty($caseModuleCount) && $caseModuleCount >= $maxCount)
{
$canNotImport[$caseID] = $caseID;
}
else
{
$canImport[$caseID] = $caseID;
}
}
return $this->dao->select('*')->from(TABLE_CASE)
->where('deleted')->eq('0')
->beginIF($browseType != 'bysearch')->andWhere('lib')->eq($libID)->fi()
->beginIF($browseType == 'bysearch')->andWhere($query)->fi()
->beginIF(count($canImport) <= count($canNotImport))->andWhere('id')->in($canImport)->fi()
->beginIF(count($canImport) > count($canNotImport) && !empty($canNotImport))->andWhere('id')->notIn($canNotImport)->fi()
->andWhere('product')->eq(0)
->orderBy($orderBy)
->page($pager)
->fetchAll('id', false);
}
/**
* 获取已经导入的用例模块。
* Get imported case modules.
*
* @param int $productID
* @param int $libID
* @param int|string $branch
* @param string $returnType
* @access public
* @return array
*/
public function getCanImportedModules(int $productID, int $libID, int|string $branch, string $returnType = 'pairs', array $libCases = array()): array
{
$importedModules = $this->dao->select('fromCaseID,module')->from(TABLE_CASE)
->where('product')->eq($productID)
->beginIF($branch != 'all')->andWhere('branch')->eq($branch)->fi()
->andWhere('fromCaseID')->ne('0')
->andWhere('deleted')->eq('0')
->fetchGroup('fromCaseID', 'module');
foreach($importedModules as $fromCaseID => $modules) $importedModules[$fromCaseID] = array_combine(array_keys($modules), array_keys($modules));
if(empty($libCases)) $libCases = $this->loadModel('caselib')->getLibCases($libID, 'all');
$modules = $this->loadModel('tree')->getOptionMenu($productID, 'case', 0, (string)$branch);
if($returnType == 'items')
{
$moduleItems = array();
foreach($modules as $moduleID => $moduleName) $moduleItems[$moduleID] = array('text' => $moduleName, 'value' => $moduleID);
}
$canImportModules = array();
foreach($libCases as $caseID => $case)
{
$caseModules = !empty($importedModules[$caseID]) ? $importedModules[$caseID] : array();
$canImportModules[$caseID] = $returnType == 'pairs' ? array_diff_key($modules, $caseModules) : array_diff_key($moduleItems, $caseModules);
if(!empty($canImportModules[$caseID]))
{
if($returnType == 'pairs')
{
$canImportModules[$caseID]['ditto'] = $this->lang->testcase->ditto;
}
else
{
$canImportModules[$caseID]['ditto'] = array('text' => $this->lang->testcase->ditto, 'value' => 'ditto');
$canImportModules[$caseID] = array_values($canImportModules[$caseID]);
}
}
if(empty($canImportModules[$caseID])) unset($canImportModules[$caseID]);
}
return $canImportModules;
}
/**
* Build search form.
*
* @param int $productID
* @param array $products
* @param int $queryID
* @param string $actionURL
* @param int $projectID
* @param int $moduleID
* @param string $branch
* @access public
* @return void
*/
public function buildSearchForm(int $productID, array $products, int $queryID, string $actionURL, int $projectID = 0, int $moduleID = 0, string $branch = 'all'): void
{
/* 获取产品列表。Get productList. */
if($this->app->tab == 'project' && !$productID)
{
if($projectID)
{
$products = $this->loadModel('product')->getProducts($projectID);
$productList = array(0 => '');
foreach($products as $product) $productList[$product->id] = $product->name;
$productList['all'] = $this->lang->product->allProductsOfProject;
}
else
{
$productList = $products;
}
$this->config->testcase->search['params']['story']['values'] = $this->loadModel('story')->getExecutionStoryPairs($projectID, 0, 'all', $moduleID, 'full', 'active');
}
else
{
$productList = array(0 => '');
$productList['all'] = $this->lang->all;
if(isset($products[$productID])) $productList[$productID] = $products[$productID];
if(empty($productID) && empty($products))
{
$products = $this->loadModel('product')->getPairs('', 0, '', 'all');
$productList += $products;
}
$this->config->testcase->search['params']['story']['values'] = $this->loadModel('story')->getProductStoryPairs($productID, $branch, array(), 'active,reviewing', 'id_desc', 0, '', 'story', false);
}
/* 获取模块列表。*/
/* Get moduleList. */
if($productID)
{
$modules = $this->loadModel('tree')->getOptionMenu($productID, 'case', 0, $branch);
}
else
{
$modules = array();
foreach($products as $id => $name) $modules += $this->loadModel('tree')->getOptionMenu($id, 'case', 0);
}
$this->config->testcase->search['params']['product']['values'] = $productList;
$this->config->testcase->search['params']['module']['values'] = $modules;
$this->config->testcase->search['params']['scene']['values'] = $this->getSceneMenu($productID, $moduleID, $branch, 0, 0, true);
$this->config->testcase->search['params']['lib']['values'] = $this->loadModel('caselib')->getLibraries();
$product = $this->loadModel('product')->getByID($productID ? $productID : (int)key($products));
if((isset($product->type) && $product->type == 'normal') || $this->app->tab == 'project')
{
unset($this->config->testcase->search['fields']['branch']);
unset($this->config->testcase->search['params']['branch']);
}
else
{
$branches = $this->loadModel('branch')->getPairs($productID, '', $projectID);
$this->config->testcase->search['fields']['branch'] = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
$this->config->testcase->search['params']['branch']['values'] = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branches + array('all' => $this->lang->branch->all);
}
if(!$this->config->testcase->needReview) unset($this->config->testcase->search['params']['status']['values']['wait']);
$this->config->testcase->search['actionURL'] = $actionURL;
$this->config->testcase->search['queryID'] = $queryID;
$this->config->testcase->search['module'] = 'testcase';
$this->loadModel('search')->setSearchParams($this->config->testcase->search);
}
/**
* 构建搜索配置。
* Build search config.
*
* @param int $productID
* @param string $branch
* @access public
* @return array
*/
public function buildSearchConfig(int $productID, string $branch = 'all'): array
{
$this->config->testcase->search['params']['story']['values'] = $this->loadModel('story')->getProductStoryPairs($productID, $branch, array(), 'active,reviewing', 'id_desc', 0, '', 'story', false);
/* 获取模块列表。*/
/* Get moduleList. */
$modules = $this->loadModel('tree')->getOptionMenu($productID, 'case', 0, $branch);
$this->config->testcase->search['params']['module']['values'] = $modules;
$this->config->testcase->search['params']['scene']['values'] = $this->getSceneMenu($productID, 0, $branch, 0, 0, true);
$this->config->testcase->search['params']['lib']['values'] = $this->loadModel('caselib')->getLibraries();
unset($this->config->testcase->search['fields']['product']);
unset($this->config->testcase->search['params']['product']);
$product = $this->loadModel('product')->getByID($productID ? $productID : 0);
if($productID)
{
if(isset($product->type) && $product->type == 'normal')
{
unset($this->config->testcase->search['fields']['branch']);
unset($this->config->testcase->search['params']['branch']);
}
else
{
$branches = $this->loadModel('branch')->getPairs($productID, '', 0);
$this->config->testcase->search['fields']['branch'] = sprintf($this->lang->product->branch, $this->lang->product->branchName[$product->type]);
$this->config->testcase->search['params']['branch']['values'] = array('' => '', BRANCH_MAIN => $this->lang->branch->main) + $branches + array('all' => $this->lang->branch->all);
}
}
if(!$this->config->testcase->needReview) unset($this->config->testcase->search['params']['status']['values']['wait']);
$_SESSION['searchParams']['module'] = 'testcase';
$searchConfig = $this->loadModel('search')->processBuildinFields('testcase', $this->config->testcase->search);
$searchConfig['params'] = $this->search->setDefaultParams('testcase', $searchConfig['fields'], $searchConfig['params']);
return $searchConfig;
}
/**
* 过滤自动测试用例的ID列表。
* Ignore auto testcase id list.
*
* @param array $caseIdList
* @access public
* @return array
*/
public function ignoreAutoCaseIdList(array $caseIdList): array
{
if(empty($caseIdList)) return array();
return $this->dao->select('id')->from(TABLE_CASE)->where('id')->in($caseIdList)->andWhere('auto')->ne('auto')->fetchPairs('id', 'id');
}
}