9 * This trait defines helper functions for testing and documenting APIv3. In
10 * particular, it supports a workflow that links a unit-test file to an
13 * - When defining a new API, write a unit test for it.
14 * - As part of the unit test, use `callAPIAndDocument($entity, $action, ...)`.
15 * - When the test executes, the inputs and outputs are logged to an example file.
16 * - You can commit this file to git.
17 * - Whenever the inputs/output change, they'll be visible in SCM/git because
18 * the example file also changes.
20 * This trait is intended for use with PHPUnit-based test cases.
26 * This function exists to wrap api functions.
27 * so we can ensure they succeed, generate and example & throw exceptions without litterering the test with checks
29 * @param string $entity
30 * @param string $action
31 * @param array $params
32 * @param string $function
33 * Pass this in to create a generated example.
35 * Pass this in to create a generated example.
36 * @param string $description
37 * @param string|null $exampleName
41 public function callAPIAndDocument($entity, $action, $params, $function, $file, $description = "", $exampleName = NULL) {
42 $params['version'] = $this->_apiversion
;
43 $result = $this->callAPISuccess($entity, $action, $params);
44 $this->documentMe($entity, $action, $params, $result, $function, $file, $description, $exampleName);
49 * Create test generated example in api/v3/examples.
51 * To turn this off (e.g. on the server) set
52 * define(DONT_DOCUMENT_TEST_CONFIG ,1);
53 * in your settings file
55 * @param string $entity
56 * @param string $action
57 * @param array $params
58 * Array as passed to civicrm_api function.
59 * @param array $result
60 * Array as received from the civicrm_api function.
61 * @param string $testFunction
62 * Calling function - generally __FUNCTION__.
63 * @param string $testFile
64 * Called from file - generally __FILE__.
65 * @param string $description
66 * Descriptive text for the example file.
67 * @param string $exampleName
68 * Name for this example file (CamelCase) - if omitted the action name will be substituted.
70 private function documentMe($entity, $action, $params, $result, $testFunction, $testFile, $description = "", $exampleName = NULL) {
71 if ($params['version'] != 3 ||
(defined('DONT_DOCUMENT_TEST_CONFIG') && DONT_DOCUMENT_TEST_CONFIG
)) {
74 $entity = _civicrm_api_get_camel_name($entity);
75 $action = strtolower($action);
77 if (empty($exampleName)) {
78 // Attempt to convert lowercase action name to CamelCase.
79 // This is clunky/imperfect due to the convention of all lowercase actions.
80 $exampleName = \CRM_Utils_String
::convertStringToCamel($action);
88 foreach ($knownPrefixes as $prefix) {
89 if (strpos($exampleName, $prefix) === 0 && $prefix != $exampleName) {
90 $exampleName[strlen($prefix)] = strtoupper($exampleName[strlen($prefix)]);
95 $this->tidyExampleResult($result);
96 if (isset($params['version'])) {
97 unset($params['version']);
99 // Format multiline description as array
101 if (is_string($description) && strlen($description)) {
102 foreach (explode("\n", $description) as $line) {
103 $desc[] = trim($line);
106 $smarty = \CRM_Core_Smarty
::singleton();
107 $smarty->assign('testFunction', $testFunction);
108 $smarty->assign('function', _civicrm_api_get_entity_name_from_camel($entity) . "_$action");
109 foreach ($params as $index => $param) {
110 if (is_string($param)) {
111 $params[$index] = addslashes($param);
114 $smarty->assign('params', $params);
115 $smarty->assign('entity', $entity);
116 $smarty->assign('testFile', basename($testFile));
117 $smarty->assign('description', $desc);
118 $smarty->assign('result', $result);
119 $smarty->assign('action', $action);
121 global $civicrm_root;
122 if (file_exists($civicrm_root . '/tests/templates/documentFunction.tpl')) {
123 if (!is_dir($civicrm_root . "/api/v3/examples/$entity")) {
124 mkdir($civicrm_root . "/api/v3/examples/$entity");
126 $f = fopen($civicrm_root . "/api/v3/examples/$entity/$exampleName.php", "w+b");
127 fwrite($f, $smarty->fetch($civicrm_root . '/tests/templates/documentFunction.tpl'));
133 * Tidy up examples array so that fields that change often ..don't
134 * and debug related fields are unset
136 * @param array $result
138 public function tidyExampleResult(&$result) {
139 if (!is_array($result)) {
143 'hash' => '67eac7789eaee00',
144 'modified_date' => '2012-11-14 16:02:35',
145 'created_date' => '2013-07-28 08:49:19',
146 'create_date' => '20120130621222105',
147 'application_received_date' => '20130728084957',
148 'in_date' => '2013-07-28 08:50:19',
149 'scheduled_date' => '20130728085413',
150 'approval_date' => '20130728085413',
151 'pledge_start_date_high' => '20130726090416',
152 'start_date' => '2013-07-29 00:00:00',
153 'event_start_date' => '2013-07-29 00:00:00',
154 'end_date' => '2013-08-04 00:00:00',
155 'event_end_date' => '2013-08-04 00:00:00',
156 'decision_date' => '20130805000000',
159 $keysToUnset = ['xdebug', 'undefined_fields'];
160 foreach ($keysToUnset as $unwantedKey) {
161 if (isset($result[$unwantedKey])) {
162 unset($result[$unwantedKey]);
165 if (isset($result['values'])) {
166 if (!is_array($result['values'])) {
169 $resultArray = &$result['values'];
171 elseif (is_array($result)) {
172 $resultArray = &$result;
178 foreach ($resultArray as $index => &$values) {
179 if (!is_array($values)) {
182 foreach ($values as $key => &$value) {
183 if (substr($key, 0, 3) == 'api' && is_array($value)) {
184 if (isset($value['is_error'])) {
185 // we have a std nested result format
186 $this->tidyExampleResult($value);
189 foreach ($value as &$nestedResult) {
190 // this is an alternative syntax for nested results a keyed array of results
191 $this->tidyExampleResult($nestedResult);
195 if (in_array($key, $keysToUnset)) {
196 unset($values[$key]);
199 if (array_key_exists($key, $fieldsToChange) && !empty($value)) {
200 $value = $fieldsToChange[$key];
202 if (is_string($value)) {
203 $value = addslashes($value);