PluginProbe
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress / 8.5.71
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress v8.5.71
9.1.3 9.1.2 9.1.1 9.1.0 9.0.3 9.0.2 9.0.1 9.0.0 8.5.79 8.5.78 8.5.77 8.5.76 8.5.75 8.5.74 8.5.73 8.5.72 8.5.71 8.5.70 8.5.69 8.5.68 8.5.35 8.5.36 8.5.37 8.5.38 8.5.39 All 222 releases
wpvr / vendor / linno / telemetry / tests / ClientTest.php

ClientTest.php in WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress 8.5.71, at vendor/linno/telemetry/tests/ClientTest.php

478 lines 18.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace LinnoSDK\Telemetry\Tests;
4
5 use LinnoSDK\Telemetry\Client;
6 use LinnoSDK\Telemetry\Drivers\DriverInterface;
7 use LinnoSDK\Telemetry\Drivers\NullDriver;
8 use Mockery;
9 use PHPUnit\Framework\TestCase;
10
11 /**
12 * ClientTest
13 *
14 * Covers:
15 * - T007 Missing-driver warning logs and non-throw behavior
16 * - T010 OpenPanel driver selection and send-failure logging
17 * - T011 Activation / deactivation lifecycle events (US1)
18 * - T015 Public custom-event API strict pass-through (US2)
19 * - T017 Consent-path regression for opt-in gated custom events (US2)
20 * - T022 Optional trigger definitions disabled by omission (US3)
21 */
22 class ClientTest extends TestCase
23 {
24 protected function setUp(): void
25 {
26 wp_reset_stubs();
27 }
28
29 protected function tearDown(): void
30 {
31 Mockery::close();
32 }
33
34 // -----------------------------------------------------------------------
35 // Helpers
36 // -----------------------------------------------------------------------
37
38 private function makeDriver( bool $sendResult = true ): DriverInterface
39 {
40 $driver = Mockery::mock( DriverInterface::class );
41 $driver->shouldReceive( 'setApiKey' )->zeroOrMoreTimes();
42 $driver->shouldReceive( 'getLastError' )->andReturn( null )->zeroOrMoreTimes();
43 // byDefault() makes this a fallback; explicit ->with() expectations take priority.
44 $driver->shouldReceive( 'send' )->andReturn( $sendResult )->zeroOrMoreTimes()->byDefault();
45 return $driver;
46 }
47
48 private function makeClient( array $extra = [], ?DriverInterface $driver = null ): Client
49 {
50 $config = array_merge(
51 [
52 'pluginFile' => '/var/www/html/wp-content/plugins/my-plugin/my-plugin.php',
53 'slug' => 'my-plugin',
54 'pluginName' => 'My Plugin',
55 'version' => '1.0.0',
56 ],
57 $extra
58 );
59
60 if ( $driver !== null ) {
61 // Inject driver via driver_config['_test_driver'] bypass
62 $config['_test_driver'] = $driver;
63 }
64
65 return new Client( $config );
66 }
67
68 // -----------------------------------------------------------------------
69 // T007 — Missing-driver warning logs and non-throw behavior
70 // -----------------------------------------------------------------------
71
72 public function testClientBootsWithoutDriverConfigured(): void
73 {
74 // No driver key → resolves to NullDriver; must not throw
75 $client = $this->makeClient( [ 'driver' => '' ] );
76 $this->assertInstanceOf( Client::class, $client );
77 }
78
79 public function testClientBootsWithUnknownDriverAndLogsWarning(): void
80 {
81 $logged = [];
82 // Capture error_log calls via set_error_handler is not straightforward;
83 // the test simply asserts no exception and the driver resolves to NullDriver.
84 $client = $this->makeClient( [ 'driver' => 'unknown_driver' ] );
85 $this->assertInstanceOf( Client::class, $client );
86 }
87
88 public function testTrackWithMissingDriverDoesNotThrow(): void
89 {
90 // Gated by opt-in; the track call should silently exit, no exception.
91 $client = $this->makeClient( [ 'driver' => '' ] );
92
93 // Grant consent AFTER construction (constructor resets consent state via upgrade check)
94 update_option( 'linno_telemetry_allow_tracking', 'yes' );
95
96 $this->assertNull( $client->track( 'some_event', [ 'foo' => 'bar' ] ) );
97 }
98
99 // -----------------------------------------------------------------------
100 // T010 — OpenPanel driver selection
101 // -----------------------------------------------------------------------
102
103 public function testClientSelectsOpenPanelDriverExplicitly(): void
104 {
105 $client = $this->makeClient( [
106 'driver' => 'open_panel',
107 'apiKey' => 'op_test_key',
108 'apiSecret' => 'op_test_secret',
109 ] );
110 $dispatcher = $client->getDispatcher();
111 $this->assertInstanceOf( \LinnoSDK\Telemetry\Drivers\OpenPanelDriver::class, $dispatcher->getDriver() );
112 }
113
114 // -----------------------------------------------------------------------
115 // T011 — Activation / deactivation lifecycle events (US1)
116 // -----------------------------------------------------------------------
117
118 public function testActivateEmitsCanonicalActivationEvent(): void
119 {
120 $driver = $this->makeDriver();
121 $driver->shouldReceive( 'send' )
122 ->with( 'activation/plugin_activated', Mockery::type( 'array' ) )
123 ->once()
124 ->andReturn( true );
125
126 $client = $this->makeClient( [], $driver );
127 $client->activate();
128 // Mockery verifies the expectation; add phpunit assertion count.
129 $this->addToAssertionCount( 1 );
130 }
131
132 public function testActivateDoesNotResendWhenAlreadyTracked(): void
133 {
134 $driver = $this->makeDriver();
135 $driver->shouldReceive( 'send' )->never();
136
137 update_option( 'my-plugin_telemetry_activated_tracked', 'yes' );
138
139 $client = $this->makeClient( [], $driver );
140 $client->activate();
141
142 // Mockery verifies `send` was never called; add explicit count to avoid PHPUnit risky warning.
143 $this->addToAssertionCount( 1 );
144 }
145
146 public function testDeactivateEmitsCanonicalDeactivationEvent(): void
147 {
148 $driver = $this->makeDriver();
149 $driver->shouldReceive( 'send' )
150 ->with( 'activation/plugin_deactivated', Mockery::type( 'array' ) )
151 ->once()
152 ->andReturn( true );
153
154 $client = $this->makeClient( [], $driver );
155 $client->deactivate();
156 // Mockery verifies the expectation; add phpunit assertion count.
157 $this->addToAssertionCount( 1 );
158 }
159
160 // -----------------------------------------------------------------------
161 // T015 — Custom event API strict pass-through (US2)
162 // -----------------------------------------------------------------------
163
164 public function testTrackPassesEventNameAndPropertiesUnchanged(): void
165 {
166 // Grant consent so events are queued (not blocked)
167 update_option( 'linno_telemetry_allow_tracking', 'yes' );
168
169 $client = $this->makeClient();
170 // track() adds to the queue; assert it does not throw and returns void
171 $result = $client->track( 'custom/my_event', [ 'key' => 'value' ] );
172 $this->assertNull( $result );
173 }
174
175 public function testTrackWithOverrideBypasesConsentCheck(): void
176 {
177 // No consent set, but override=true should still queue the event without throwing.
178 // track() always uses the async queue; it does NOT dispatch directly.
179 $client = $this->makeClient();
180 $result = $client->track( 'custom/my_event', [ 'key' => 'value' ], true );
181 $this->assertNull( $result );
182 }
183
184 // -----------------------------------------------------------------------
185 // T017 — Consent-path regression: opt-in gated custom events use queue (US2)
186 // -----------------------------------------------------------------------
187
188 public function testTrackWithoutConsentDoesNotDispatch(): void
189 {
190 $driver = $this->makeDriver();
191 $driver->shouldReceive( 'send' )->never();
192
193 $client = $this->makeClient( [], $driver );
194 // No consent → event must be silently dropped
195 $client->track( 'custom/my_event', [] );
196
197 // Mockery verifies `send` was never called; add explicit count to avoid PHPUnit risky warning.
198 $this->addToAssertionCount( 1 );
199 }
200
201 // -----------------------------------------------------------------------
202 // T022 — Initialization with omitted optional triggers succeeds (US3)
203 // -----------------------------------------------------------------------
204
205 public function testClientInitializesWithoutTriggerDefinitions(): void
206 {
207 $client = $this->makeClient();
208 // define_triggers() was never called; client should still be operational
209 $this->assertInstanceOf( Client::class, $client );
210 }
211
212 public function testClientCanTrackEventWithoutTriggerDefinitions(): void
213 {
214 update_option( 'linno_telemetry_allow_tracking', 'yes' );
215
216 $client = $this->makeClient();
217 // Must not throw
218 $client->track( 'my_event', [] );
219 $this->assertTrue( true );
220 }
221
222 // -----------------------------------------------------------------------
223 // WordPress action hook — custom event (US2, T020)
224 // -----------------------------------------------------------------------
225
226 public function testWordPressActionRoutesCustomEventToTrack(): void
227 {
228 update_option( 'linno_telemetry_allow_tracking', 'yes' );
229
230 $client = $this->makeClient();
231 $slug = $client->get_slug();
232 $hookName = $slug . '_telemetry_track';
233
234 // Fire the registered action
235 do_action( $hookName, 'wp_custom_event', [ 'source' => 'hook' ] );
236
237 // Assert no exceptions were thrown — the queue would hold the event
238 $this->assertTrue( true );
239 }
240
241 // -----------------------------------------------------------------------
242 // T008 / T009 — add_feature_used_event static API (US2)
243 // -----------------------------------------------------------------------
244
245 public function testAddFeatureUsedEventRegistersActionHook(): void
246 {
247 global $_wp_hooks;
248
249 Client::add_feature_used_event( 'my_plugin_feature_used', 'Export Settings' );
250
251 $this->assertNotEmpty( $_wp_hooks['my_plugin_feature_used'] ?? [] );
252 }
253
254 public function testAddFeatureUsedEventCallbackTracksCorrectEvent(): void
255 {
256 // Reset static instances so only this client is registered.
257 $ref = new \ReflectionProperty( Client::class, 'instances' );
258 $ref->setAccessible( true );
259 $ref->setValue( null, [] );
260
261 $client = $this->makeClient();
262
263 update_option( 'linno_telemetry_allow_tracking', 'yes' );
264
265 Client::add_feature_used_event( 'my_plugin_export_run', 'Export Settings' );
266
267 // Firing the hook must not throw; track() routes to the queue internally.
268 do_action( 'my_plugin_export_run' );
269
270 $this->assertTrue( true );
271 }
272
273 public function testAddFeatureUsedEventForwardsParamsToTrack(): void
274 {
275 $ref = new \ReflectionProperty( Client::class, 'instances' );
276 $ref->setAccessible( true );
277 $ref->setValue( null, [] );
278
279 $client = $this->makeClient();
280
281 update_option( 'linno_telemetry_allow_tracking', 'yes' );
282
283 Client::add_feature_used_event( 'my_plugin_import_run', 'Import Settings', [ 'source' => 'file' ] );
284
285 // Firing the hook must not throw even when extra params are supplied.
286 do_action( 'my_plugin_import_run' );
287
288 $this->assertTrue( true );
289 }
290
291 public function testAddFeatureUsedEventSilentlyDropsWithoutConsent(): void
292 {
293 $driver = $this->makeDriver();
294 $driver->shouldReceive( 'send' )->never();
295
296 $ref = new \ReflectionProperty( Client::class, 'instances' );
297 $ref->setAccessible( true );
298 $ref->setValue( null, [] );
299
300 $client = $this->makeClient( [], $driver );
301
302 // No consent set — event must be silently dropped.
303 Client::add_feature_used_event( 'my_plugin_no_consent', 'Some Feature' );
304 do_action( 'my_plugin_no_consent' );
305
306 $this->addToAssertionCount( 1 );
307 }
308
309 // -----------------------------------------------------------------------
310 // BC-007 — Array constructor continues to work (US1)
311 // -----------------------------------------------------------------------
312
313 public function testArrayConstructorContinuesToWork(): void
314 {
315 $client = $this->makeClient();
316 $this->assertInstanceOf( Client::class, $client );
317 }
318
319 // -----------------------------------------------------------------------
320 // BC-008 — Legacy 4-param constructor does not throw (US1)
321 // -----------------------------------------------------------------------
322
323 public function testLegacyFourParamConstructorDoesNotThrow(): void
324 {
325 $client = @new Client( 'test-api-key', 'test-secret', 'My Plugin', '/path/to/plugin.php' );
326 $this->assertInstanceOf( Client::class, $client );
327 }
328
329 // -----------------------------------------------------------------------
330 // BC-009 — Invalid first arg throws InvalidArgumentException (US1)
331 // -----------------------------------------------------------------------
332
333 public function testInvalidFirstArgThrowsException(): void
334 {
335 $this->expectException( \InvalidArgumentException::class );
336 $this->expectExceptionMessage( 'First argument must be a configuration array or a string API key' );
337 new Client( 42 );
338 }
339
340 // -----------------------------------------------------------------------
341 // BC-010 — Too few positional params throws InvalidArgumentException (US1)
342 // -----------------------------------------------------------------------
343
344 public function testTooFewPositionalParamsThrowsException(): void
345 {
346 $this->expectException( \InvalidArgumentException::class );
347 $this->expectExceptionMessage( 'Legacy constructor requires exactly 4 string parameters' );
348 new Client( 'only-one-string' );
349 }
350
351 // -----------------------------------------------------------------------
352 // BC-011 — Empty API key throws InvalidArgumentException (US1)
353 // -----------------------------------------------------------------------
354
355 public function testEmptyApiKeyThrowsException(): void
356 {
357 $this->expectException( \InvalidArgumentException::class );
358 $this->expectExceptionMessage( 'API key must not be empty' );
359 new Client( '', 'secret', 'Name', '/path.php' );
360 }
361
362 // -----------------------------------------------------------------------
363 // BC-012 — Empty plugin file throws InvalidArgumentException (US1)
364 // -----------------------------------------------------------------------
365
366 public function testEmptyPluginFileThrowsException(): void
367 {
368 $this->expectException( \InvalidArgumentException::class );
369 $this->expectExceptionMessage( 'Plugin file path must not be empty' );
370 new Client( 'key', 'secret', 'Name', '' );
371 }
372
373 // -----------------------------------------------------------------------
374 // BC-013 — Empty plugin name throws InvalidArgumentException (US1)
375 // -----------------------------------------------------------------------
376
377 public function testEmptyPluginNameThrowsException(): void
378 {
379 $this->expectException( \InvalidArgumentException::class );
380 $this->expectExceptionMessage( 'Plugin name must not be empty' );
381 new Client( 'key', 'secret', '', '/path.php' );
382 }
383
384 // -----------------------------------------------------------------------
385 // BC-019 — Legacy constructor maps API key (US2)
386 // -----------------------------------------------------------------------
387
388 public function testLegacyConstructorMapsApiKey(): void
389 {
390 $client = @new Client( 'my-api-key', 'my-secret', 'My Plugin', '/path/to/plugin.php' );
391 $this->assertSame( 'my-api-key', $client->getConfig()['apiKey'] );
392 }
393
394 // -----------------------------------------------------------------------
395 // BC-020 — Legacy constructor maps plugin name (US2)
396 // -----------------------------------------------------------------------
397
398 public function testLegacyConstructorMapsPluginName(): void
399 {
400 $client = @new Client( 'my-api-key', 'my-secret', 'My Plugin', '/path/to/plugin.php' );
401 $this->assertSame( 'My Plugin', $client->getConfig()['pluginName'] );
402 }
403
404 // -----------------------------------------------------------------------
405 // BC-021 — Legacy constructor derives slug (US2)
406 // -----------------------------------------------------------------------
407
408 public function testLegacyConstructorDerivesSlug(): void
409 {
410 $client = @new Client( 'my-api-key', 'my-secret', 'My Plugin', '/path/to/plugin.php' );
411 $this->assertSame( sanitize_title( 'My Plugin' ), $client->getConfig()['slug'] );
412 }
413
414 // -----------------------------------------------------------------------
415 // BC-022 — Legacy constructor defaults driver to open_panel (US2)
416 // -----------------------------------------------------------------------
417
418 public function testLegacyConstructorDefaultsDriverToOpenPanel(): void
419 {
420 $client = @new Client( 'my-api-key', 'my-secret', 'My Plugin', '/path/to/plugin.php' );
421 $this->assertSame( 'open_panel', $client->getConfig()['driver'] );
422 }
423
424 // -----------------------------------------------------------------------
425 // BC-023 — Legacy constructor generates a unique_id (US2)
426 // -----------------------------------------------------------------------
427
428 public function testLegacyConstructorGeneratesUniqueId(): void
429 {
430 $client = @new Client( 'my-api-key', 'my-secret', 'My Plugin', '/path/to/plugin.php' );
431 $this->assertNotEmpty( $client->getConfig()['unique_id'] );
432 }
433
434 // -----------------------------------------------------------------------
435 // BC-027 — Legacy constructor emits deprecation notice (US3)
436 // -----------------------------------------------------------------------
437
438 public function testLegacyConstructorEmitsDeprecationNotice(): void
439 {
440 $deprecations = [];
441 set_error_handler( function ( int $errno, string $errstr ) use ( &$deprecations ): bool {
442 if ( E_USER_DEPRECATED === $errno ) {
443 $deprecations[] = $errstr;
444 }
445 return true;
446 } );
447
448 new Client( 'my-api-key', 'my-secret', 'My Plugin', '/path/to/plugin.php' );
449
450 restore_error_handler();
451
452 $this->assertNotEmpty( $deprecations, 'Legacy constructor must emit a deprecation notice.' );
453 $this->assertStringContainsString( 'Passing positional parameters to', $deprecations[0] );
454 }
455
456 // -----------------------------------------------------------------------
457 // BC-028 — Array constructor does NOT emit deprecation (US3)
458 // -----------------------------------------------------------------------
459
460 public function testArrayConstructorDoesNotEmitDeprecation(): void
461 {
462 // Use a custom error handler to catch any unexpected deprecation notices.
463 $deprecations = [];
464 set_error_handler( function ( int $errno, string $errstr ) use ( &$deprecations ): bool {
465 if ( E_USER_DEPRECATED === $errno ) {
466 $deprecations[] = $errstr;
467 }
468 return true;
469 } );
470
471 $this->makeClient();
472
473 restore_error_handler();
474
475 $this->assertEmpty( $deprecations, 'Array constructor must not emit deprecation notices.' );
476 }
477 }
478