Commit | Line | Data |
---|---|---|
195557d5 TO |
1 | <?php |
2 | ||
3 | namespace Civi\Test; | |
4 | ||
5 | /** | |
6 | * Class Api3DocTrait | |
7 | * @package Civi\Test | |
8 | * | |
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 | |
11 | * example data file: | |
12 | * | |
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. | |
19 | * | |
20 | * This trait is intended for use with PHPUnit-based test cases. | |
21 | */ | |
22 | trait Api3DocTrait { | |
23 | use Api3TestTrait; | |
24 | ||
25 | /** | |
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 | |
28 | * | |
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. | |
34 | * @param string $file | |
35 | * Pass this in to create a generated example. | |
36 | * @param string $description | |
37 | * @param string|null $exampleName | |
38 | * | |
39 | * @return array|int | |
40 | */ | |
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); | |
45 | return $result; | |
46 | } | |
47 | ||
48 | /** | |
49 | * Create test generated example in api/v3/examples. | |
50 | * | |
51 | * To turn this off (e.g. on the server) set | |
52 | * define(DONT_DOCUMENT_TEST_CONFIG ,1); | |
53 | * in your settings file | |
54 | * | |
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. | |
69 | */ | |
70 | private function documentMe($entity, $action, $params, $result, $testFunction, $testFile, $description = "", $exampleName = NULL) { | |
71 | if (defined('DONT_DOCUMENT_TEST_CONFIG') && DONT_DOCUMENT_TEST_CONFIG) { | |
72 | return; | |
73 | } | |
74 | $entity = _civicrm_api_get_camel_name($entity); | |
75 | $action = strtolower($action); | |
76 | ||
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); | |
81 | $knownPrefixes = array( | |
82 | 'Get', | |
83 | 'Set', | |
84 | 'Create', | |
85 | 'Update', | |
86 | 'Send', | |
87 | ); | |
88 | foreach ($knownPrefixes as $prefix) { | |
89 | if (strpos($exampleName, $prefix) === 0 && $prefix != $exampleName) { | |
90 | $exampleName[strlen($prefix)] = strtoupper($exampleName[strlen($prefix)]); | |
91 | } | |
92 | } | |
93 | } | |
94 | ||
95 | $this->tidyExampleResult($result); | |
96 | if (isset($params['version'])) { | |
97 | unset($params['version']); | |
98 | } | |
99 | // Format multiline description as array | |
100 | $desc = array(); | |
101 | if (is_string($description) && strlen($description)) { | |
102 | foreach (explode("\n", $description) as $line) { | |
103 | $desc[] = trim($line); | |
104 | } | |
105 | } | |
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); | |
112 | } | |
113 | } | |
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); | |
120 | ||
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"); | |
125 | } | |
126 | $f = fopen($civicrm_root . "/api/v3/examples/$entity/$exampleName.php", "w+b"); | |
127 | fwrite($f, $smarty->fetch($civicrm_root . '/tests/templates/documentFunction.tpl')); | |
128 | fclose($f); | |
129 | } | |
130 | } | |
131 | ||
132 | /** | |
133 | * Tidy up examples array so that fields that change often ..don't | |
134 | * and debug related fields are unset | |
135 | * | |
136 | * @param array $result | |
137 | */ | |
138 | public function tidyExampleResult(&$result) { | |
139 | if (!is_array($result)) { | |
140 | return; | |
141 | } | |
142 | $fieldsToChange = array( | |
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', | |
157 | ); | |
158 | ||
159 | $keysToUnset = array('xdebug', 'undefined_fields'); | |
160 | foreach ($keysToUnset as $unwantedKey) { | |
161 | if (isset($result[$unwantedKey])) { | |
162 | unset($result[$unwantedKey]); | |
163 | } | |
164 | } | |
165 | if (isset($result['values'])) { | |
166 | if (!is_array($result['values'])) { | |
167 | return; | |
168 | } | |
169 | $resultArray = &$result['values']; | |
170 | } | |
171 | elseif (is_array($result)) { | |
172 | $resultArray = &$result; | |
173 | } | |
174 | else { | |
175 | return; | |
176 | } | |
177 | ||
178 | foreach ($resultArray as $index => &$values) { | |
179 | if (!is_array($values)) { | |
180 | continue; | |
181 | } | |
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); | |
187 | } | |
188 | else { | |
189 | foreach ($value as &$nestedResult) { | |
190 | // this is an alternative syntax for nested results a keyed array of results | |
191 | $this->tidyExampleResult($nestedResult); | |
192 | } | |
193 | } | |
194 | } | |
195 | if (in_array($key, $keysToUnset)) { | |
196 | unset($values[$key]); | |
197 | break; | |
198 | } | |
199 | if (array_key_exists($key, $fieldsToChange) && !empty($value)) { | |
200 | $value = $fieldsToChange[$key]; | |
201 | } | |
202 | if (is_string($value)) { | |
203 | $value = addslashes($value); | |
204 | } | |
205 | } | |
206 | } | |
207 | } | |
208 | ||
209 | } |