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.ex.php", "w+b");
127 $contents = $smarty->fetch($civicrm_root . '/tests/templates/documentFunction.tpl');
128 $contents = \CRM_Core_CodeGen_Util_ArraySyntaxConverter
::convert($contents);
129 fwrite($f, $contents);
135 * Tidy up examples array so that fields that change often ..don't
136 * and debug related fields are unset
138 * @param array $result
140 public function tidyExampleResult(&$result) {
141 if (!is_array($result)) {
145 'hash' => '67eac7789eaee00',
146 'modified_date' => '2012-11-14 16:02:35',
147 'created_date' => '2013-07-28 08:49:19',
148 'create_date' => '20120130621222105',
149 'application_received_date' => '20130728084957',
150 'in_date' => '2013-07-28 08:50:19',
151 'scheduled_date' => '20130728085413',
152 'approval_date' => '20130728085413',
153 'pledge_start_date_high' => '20130726090416',
154 'start_date' => '2013-07-29 00:00:00',
155 'event_start_date' => '2013-07-29 00:00:00',
156 'end_date' => '2013-08-04 00:00:00',
157 'event_end_date' => '2013-08-04 00:00:00',
158 'decision_date' => '20130805000000',
161 $keysToUnset = ['xdebug', 'undefined_fields'];
162 foreach ($keysToUnset as $unwantedKey) {
163 if (isset($result[$unwantedKey])) {
164 unset($result[$unwantedKey]);
167 if (isset($result['values'])) {
168 if (!is_array($result['values'])) {
171 $resultArray = &$result['values'];
173 elseif (is_array($result)) {
174 $resultArray = &$result;
180 foreach ($resultArray as $index => &$values) {
181 if (!is_array($values)) {
184 foreach ($values as $key => &$value) {
185 if (substr($key, 0, 3) == 'api' && is_array($value)) {
186 if (isset($value['is_error'])) {
187 // we have a std nested result format
188 $this->tidyExampleResult($value);
191 foreach ($value as &$nestedResult) {
192 // this is an alternative syntax for nested results a keyed array of results
193 $this->tidyExampleResult($nestedResult);
197 if (in_array($key, $keysToUnset)) {
198 unset($values[$key]);
201 if (array_key_exists($key, $fieldsToChange) && !empty($value)) {
202 $value = $fieldsToChange[$key];
204 if (is_string($value)) {
205 $value = addslashes($value);