-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathApiTest.php
More file actions
375 lines (336 loc) · 12.1 KB
/
Copy pathApiTest.php
File metadata and controls
375 lines (336 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
<?php
declare(strict_types=1);
namespace Detain\MyAdminWebhosting\Tests;
use PHPUnit\Framework\TestCase;
/**
* Test suite for the API functions in api.php.
*
* Since these functions depend heavily on global state and database calls,
* tests focus on static analysis: function existence, signatures, and
* parameter definitions.
*
* @package Detain\MyAdminWebhosting\Tests
*/
class ApiTest extends TestCase
{
/**
* Path to the API file.
*
* @var string
*/
private static string $apiFile;
/**
* Contents of the API file.
*
* @var string
*/
private static string $apiContent;
/**
* Resolve the API file path once for the test suite.
*
* @return void
*/
public static function setUpBeforeClass(): void
{
parent::setUpBeforeClass();
self::$apiFile = dirname(__DIR__) . '/src/api.php';
$content = file_get_contents(self::$apiFile);
self::$apiContent = $content !== false ? $content : '';
}
// ---------------------------------------------------------------
// File Existence
// ---------------------------------------------------------------
/**
* Test that the api.php source file exists.
*
* @return void
*/
public function testApiFileExists(): void
{
$this->assertFileExists(self::$apiFile);
}
/**
* Test that api.php is readable and non-empty.
*
* @return void
*/
public function testApiFileIsReadableAndNonEmpty(): void
{
$this->assertNotEmpty(self::$apiContent, 'api.php should be non-empty.');
}
/**
* Test that api.php has no syntax errors using php -l.
*
* @return void
*/
public function testApiFileIsValidPhp(): void
{
$output = [];
$exitCode = 0;
$escapedPath = escapeshellarg(self::$apiFile);
// Using exec for PHP lint check - input is a known file path, not user data
exec("php -l {$escapedPath} 2>&1", $output, $exitCode);
$this->assertSame(0, $exitCode, 'api.php should have no syntax errors: ' . implode("\n", $output));
}
/**
* Test that api.php contains the expected function definitions via token analysis.
*
* @return void
*/
public function testApiFileContainsExpectedFunctions(): void
{
$this->assertStringContainsString('function api_place_buy_website', self::$apiContent);
$this->assertStringContainsString('function api_validate_buy_website', self::$apiContent);
}
/**
* api_place_buy_website() must hand validate_buy_website() a literal TOS agreement.
*
* It used to pass $tos, which that function never declares as a parameter and never
* assigns -- so it arrived as null, cast to '', and failed validate_buy_website()'s
* `in_array(strtolower($tos), ['yes', 'true'])` check on every single call. The function
* could not place an order at all; it returned "You must agree to the terms of service"
* every time. Nothing reported that, because the failure looked like an ordinary
* validation error rather than a defect.
*
* Asserted on the source rather than by calling it, because reaching the check needs the
* whole MyAdmin order stack. The regression this guards against is textual anyway: someone
* reinstating the variable.
*
* @return void
*/
public function testPlaceBuyWebsitePassesALiteralTosAgreement(): void
{
$this->assertMatchesRegularExpression(
'/validate_buy_website\(\$custid, \$period, \$coupon, \x27(yes|true)\x27,/',
self::$apiContent,
'api_place_buy_website() must pass a literal TOS agreement; an undefined $tos fails validation every time'
);
}
// ---------------------------------------------------------------
// Function Signature Static Analysis (via token parsing)
// ---------------------------------------------------------------
/**
* Test that api_place_buy_website has the correct parameter count by parsing tokens.
*
* @return void
*/
public function testPlaceBuyWebsiteParameterCount(): void
{
$params = $this->extractFunctionParams('api_place_buy_website');
$this->assertCount(6, $params, 'api_place_buy_website should accept 6 parameters.');
}
/**
* Test that api_validate_buy_website has the correct parameter count by parsing tokens.
*
* @return void
*/
public function testValidateBuyWebsiteParameterCount(): void
{
$params = $this->extractFunctionParams('api_validate_buy_website');
$this->assertCount(7, $params, 'api_validate_buy_website should accept 7 parameters.');
}
/**
* Test that api_place_buy_website parameter names match expectations.
*
* @return void
*/
public function testPlaceBuyWebsiteParameterNames(): void
{
$params = $this->extractFunctionParams('api_place_buy_website');
$expected = ['$service_type', '$period', '$hostname', '$coupon', '$password', '$script'];
$this->assertSame($expected, $params);
}
/**
* Test that api_validate_buy_website parameter names match expectations.
*
* @return void
*/
public function testValidateBuyWebsiteParameterNames(): void
{
$params = $this->extractFunctionParams('api_validate_buy_website');
$expected = ['$period', '$coupon', '$tos', '$service_type', '$hostname', '$password', '$script'];
$this->assertSame($expected, $params);
}
// ---------------------------------------------------------------
// Docblock Analysis
// ---------------------------------------------------------------
/**
* Test that api_place_buy_website has a docblock.
*
* @return void
*/
public function testPlaceBuyWebsiteHasDocblock(): void
{
$this->assertMatchesRegularExpression(
'/\/\*\*[\s\S]*?\*\/\s*function\s+api_place_buy_website/',
self::$apiContent,
'api_place_buy_website should have a docblock.'
);
}
/**
* Test that api_validate_buy_website has a docblock.
*
* @return void
*/
public function testValidateBuyWebsiteHasDocblock(): void
{
$this->assertMatchesRegularExpression(
'/\/\*\*[\s\S]*?\*\/\s*function\s+api_validate_buy_website/',
self::$apiContent,
'api_validate_buy_website should have a docblock.'
);
}
/**
* Test that the api_place_buy_website docblock documents a return type.
*
* @return void
*/
public function testPlaceBuyWebsiteDocblockHasReturn(): void
{
preg_match('/(\/\*\*[\s\S]*?\*\/)\s*function\s+api_place_buy_website/', self::$apiContent, $matches);
$this->assertNotEmpty($matches, 'Should find docblock for api_place_buy_website.');
$this->assertStringContainsString('@return', $matches[1]);
}
/**
* Test that the api_validate_buy_website docblock documents a return type.
*
* @return void
*/
public function testValidateBuyWebsiteDocblockHasReturn(): void
{
preg_match('/(\/\*\*[\s\S]*?\*\/)\s*function\s+api_validate_buy_website/', self::$apiContent, $matches);
$this->assertNotEmpty($matches, 'Should find docblock for api_validate_buy_website.');
$this->assertStringContainsString('@return', $matches[1]);
}
// ---------------------------------------------------------------
// Return Structure Static Analysis
// ---------------------------------------------------------------
/**
* Test that api_place_buy_website returns an array with status keys.
*
* @return void
*/
public function testPlaceBuyWebsiteReturnsStatusArray(): void
{
$this->assertStringContainsString("\$return['status']", self::$apiContent);
$this->assertStringContainsString("\$return['status_text']", self::$apiContent);
}
/**
* Test that both functions use 'ok' and 'error' status values.
*
* @return void
*/
public function testFunctionsUseExpectedStatusValues(): void
{
$this->assertStringContainsString("'ok'", self::$apiContent);
$this->assertStringContainsString("'error'", self::$apiContent);
}
// ---------------------------------------------------------------
// Dependency Analysis
// ---------------------------------------------------------------
/**
* Test that api_place_buy_website calls function_requirements for its dependencies.
*
* @return void
*/
public function testPlaceBuyWebsiteCallsFunctionRequirements(): void
{
$this->assertStringContainsString("function_requirements('validate_buy_website')", self::$apiContent);
$this->assertStringContainsString("function_requirements('place_buy_website')", self::$apiContent);
}
/**
* Test that api_validate_buy_website calls function_requirements for validation.
*
* @return void
*/
public function testValidateBuyWebsiteCallsFunctionRequirements(): void
{
preg_match(
'/function\s+api_validate_buy_website[\s\S]*?^}/m',
self::$apiContent,
$matches
);
$this->assertNotEmpty($matches);
$this->assertStringContainsString("function_requirements('validate_buy_website')", $matches[0]);
}
/**
* Test that both API functions access the App session for custid.
*
* @return void
*/
public function testBothFunctionsAccessGlobalSession(): void
{
$count = substr_count(self::$apiContent, "get_custid(\\MyAdmin\App::session()->account_id, 'vps')");
$this->assertSame(2, $count, 'Both API functions should call get_custid via global session.');
}
/**
* Test that api_place_buy_website has a default parameter value for $script.
*
* @return void
*/
public function testPlaceBuyWebsiteScriptDefaultValue(): void
{
preg_match('/function\s+api_place_buy_website\s*\(([^)]+)\)/', self::$apiContent, $matches);
$this->assertNotEmpty($matches);
$this->assertStringContainsString('$script = 0', $matches[1]);
}
// ---------------------------------------------------------------
// File Structure Tests
// ---------------------------------------------------------------
/**
* Test that api.php starts with a PHP open tag.
*
* @return void
*/
public function testFileStartsWithPhpTag(): void
{
$this->assertStringStartsWith('<?php', self::$apiContent);
}
/**
* Test that api.php does not declare a namespace (procedural functions).
*
* @return void
*/
public function testFileHasNoNamespace(): void
{
$this->assertStringNotContainsString('namespace ', self::$apiContent);
}
/**
* Test that api.php defines exactly 2 functions.
*
* @return void
*/
public function testFileDefinesExactlyTwoFunctions(): void
{
$count = preg_match_all('/^\s*function\s+\w+\s*\(/m', self::$apiContent);
$this->assertSame(2, $count, 'api.php should define exactly 2 functions.');
}
// ---------------------------------------------------------------
// Helper Methods
// ---------------------------------------------------------------
/**
* Extract parameter names from a function definition by parsing the source file.
*
* @param string $functionName The function name to find.
* @return array<int, string> List of parameter variable names.
*/
private function extractFunctionParams(string $functionName): array
{
$pattern = '/function\s+' . preg_quote($functionName, '/') . '\s*\(([^)]*)\)/';
if (!preg_match($pattern, self::$apiContent, $matches)) {
return [];
}
$paramString = trim($matches[1]);
if ($paramString === '') {
return [];
}
$params = [];
foreach (explode(',', $paramString) as $param) {
$param = trim($param);
if (preg_match('/(\$\w+)/', $param, $varMatch)) {
$params[] = $varMatch[1];
}
}
return $params;
}
}