Completed
Pull Request — master (#551)
by Maxence
02:37
created

levelRemoteMemberToAdminUsingRemoteAdmin()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
6
/**
7
 * Circles - Bring cloud-users closer together.
8
 *
9
 * This file is licensed under the Affero General Public License version 3 or
10
 * later. See the COPYING file.
11
 *
12
 * @author Maxence Lange <[email protected]>
13
 * @copyright 2021
14
 * @license GNU AGPL version 3 or any later version
15
 *
16
 * This program is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License as
18
 * published by the Free Software Foundation, either version 3 of the
19
 * License, or (at your option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
28
 *
29
 */
30
31
32
namespace OCA\Circles\Command;
33
34
35
use daita\MySmallPhpTools\Exceptions\InvalidItemException;
36
use daita\MySmallPhpTools\Exceptions\ItemNotFoundException;
37
use daita\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Deserialize;
38
use daita\MySmallPhpTools\Traits\TArrayTools;
39
use daita\MySmallPhpTools\Traits\TStringTools;
40
use Exception;
41
use OC\Core\Command\Base;
42
use OCA\Circles\AppInfo\Application;
43
use OCA\Circles\Db\CoreRequestBuilder;
44
use OCA\Circles\Exceptions\CircleNotFoundException;
45
use OCA\Circles\Model\Circle;
46
use OCA\Circles\Model\FederatedUser;
47
use OCA\Circles\Model\Member;
48
use OCA\Circles\Service\ConfigService;
49
use Symfony\Component\Console\Input\InputArgument;
50
use Symfony\Component\Console\Input\InputInterface;
51
use Symfony\Component\Console\Input\InputOption;
52
use Symfony\Component\Console\Output\OutputInterface;
53
use Symfony\Component\Process\Process;
54
55
56
/**
57
 * Class CirclesTest
58
 *
59
 * @package OCA\Circles\Command
60
 */
61
class CirclesTest extends Base {
62
63
64
	use TArrayTools;
65
	use TStringTools;
66
	use TNC22Deserialize;
67
68
69
	static $INSTANCES = [
70
		'global-scale-1',
71
		'global-scale-2',
72
		'global-scale-3',
73
		'passive',
74
		'external',
75
		'trusted'
76
	];
77
78
79
	static $TEST_CIRCLES = [
80
		'test_001'
81
	];
82
83
84
	/** @var CoreRequestBuilder */
85
	private $coreQueryBuilder;
86
87
	/** @var ConfigService */
88
	private $configService;
89
90
91
	/** @var InputInterface */
92
	private $input;
93
94
	/** @var OutputInterface */
95
	private $output;
96
97
	/** @var array */
98
	private $config = [];
99
100
	/** @var string */
101
	private $local = '';
102
103
	/** @var bool */
104
	private $pOn = false;
105
106
	/** @var array */
107
	private $circles = [];
108
109
	/** @var array */
110
	private $federatedUsers = [];
111
112
113
	/**
114
	 * CirclesTest constructor.
115
	 *
116
	 * @param CoreRequestBuilder $coreQueryBuilder
117
	 * @param ConfigService $configService
118
	 */
119
	public function __construct(CoreRequestBuilder $coreQueryBuilder, ConfigService $configService) {
120
		parent::__construct();
121
122
		$this->coreQueryBuilder = $coreQueryBuilder;
123
		$this->configService = $configService;
124
	}
125
126
127
	/**
128
	 *
129
	 */
130
	protected function configure() {
131
		parent::configure();
132
		$this->setName('circles:test')
133
			 ->setDescription('testing some features')
134
			 ->addArgument('deprecated', InputArgument::OPTIONAL, '')
135
			 ->addOption(
136
				 'are-you-aware-this-will-delete-all-my-data', '', InputOption::VALUE_REQUIRED,
137
				 'Well, are you ?', ''
138
			 )
139
			 ->addOption('skip-init', '', InputOption::VALUE_NONE, 'Bypass Initialisation')
140
			 ->addOption('skip-setup', '', InputOption::VALUE_NONE, 'Bypass Circles Setup')
141
			 ->addOption('only-setup', '', InputOption::VALUE_NONE, 'Stop after Circles Setup, pre-Sync');
142
	}
143
144
145
	/**
146
	 * @param InputInterface $input
147
	 * @param OutputInterface $output
148
	 *
149
	 * @return int
150
	 * @throws Exception
151
	 */
152
	protected function execute(InputInterface $input, OutputInterface $output): int {
153
		$this->input = $input;
154
		$this->output = $output;
155
156
		if ($input->getOption('are-you-aware-this-will-delete-all-my-data') === 'yes-i-am') {
157
			try {
158
				$this->testCirclesApp();
159
			} catch (Exception $e) {
160
				if ($this->pOn) {
161
					$message = ($e->getMessage() !== '') ? $e->getMessage() : get_class($e);
162
					$this->output->writeln('<error>' . $message . '</error>');
163
				} else {
164
					throw $e;
165
				}
166
			}
167
168
			return 0;
169
		}
170
171
		$output->writeln('');
172
		$output->writeln(
173
			'<error>Since Nextcloud 22, this command have changed, please read the message below:</error>'
174
		);
175
		$output->writeln('<error>This new command is to test the integrity of the Circles App.</error>');
176
		$output->writeln(
177
			'<error>Running this command will REMOVE all your current configuration and all your current Circles.</error>'
178
		);
179
		$output->writeln('<error>There is a huge probability that you do not want to do that.</error>');
180
		$output->writeln('');
181
		$output->writeln(
182
			'<error>The old testing command you might looking for have moved to "./occ circles:check"</error>'
183
		);
184
		$output->writeln('');
185
186
		return 0;
187
	}
188
189
190
	/**
191
	 * @throws CircleNotFoundException
192
	 * @throws InvalidItemException
193
	 * @throws ItemNotFoundException
194
	 */
195
	private function testCirclesApp() {
196
		$this->t('Bootup');
197
		$this->loadConfiguration();
198
199
		if (!$this->input->getOption('skip-setup')) {
200
			if (!$this->input->getOption('skip-init')) {
201
				$this->t('Initialisation Nextcloud');
202
				$this->initEnvironment();
203
			}
204
205
			$this->t('Initialisation Circles App');
206
			$this->reloadCirclesApp();
207
			$this->configureCirclesApp();
208
			$this->confirmVersion();
209
			$this->confirmEmptyCircles();
210
211
			if ($this->input->getOption('only-setup')) {
212
				return;
213
			}
214
215
			$this->syncCircles();
216
217
			$this->t('Fresh installation status');
218
			$this->statusFreshInstances();
219
			$this->createRemoteLink();
220
		}
221
222
		$this->t('Building Local Database');
223
		$this->buildingLocalDatabase();
224
225
		$this->t('Testing Basic Circle Creation');
226
		$this->circleCreation001();
227
228
		$this->t('Adding local users and moderators');
229
		$this->addLocalMemberByUserId();
230
//		$this->addLocalMemberBySingleId();
231
//		$this->addLocalMemberUsingMember();
232
//		$this->levelLocalMemberToModerator();
233
//		$this->addRemoteMemberUsingModerator();
234
//		$this->addRemoteMemberUsingRemoteMember();
235
//		$this->levelRemoteMemberToAdmin();
236
//		$this->addRemoteMemberUsingRemoteAdmin();
237
//
238
	}
239
240
241
	/**
242
	 * @throws ItemNotFoundException
243
	 */
244
	private function loadConfiguration() {
245
		$this->p('Loading configuration');
246
		$configuration = file_get_contents(__DIR__ . '/../../testConfiguration.json');
247
		$this->config = json_decode($configuration, true);
0 ignored issues
show
Documentation Bug introduced by
It seems like json_decode($configuration, true) of type * is incompatible with the declared type array of property $config.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
248
		$this->r(true, 'testConfiguration.json');
249
250
		$this->p('Checking configuration');
251
		foreach (self::$INSTANCES as $instance) {
252
			$cloudId = $this->getConfig($instance, 'config.frontal_cloud_id');
253
			if ($this->configService->isLocalInstance($cloudId)) {
254
				$this->local = $instance;
255
			}
256
		}
257
		$this->r();
258
259
		$this->p('Checking local');
260
		if ($this->local === '') {
261
			throw new ItemNotFoundException('local not defined');
262
		}
263
		$this->r(true, $this->local);
264
	}
265
266
267
	/**
268
	 * @throws ItemNotFoundException
269
	 */
270
	private function initEnvironment() {
271
		$this->p('Disabling Password Policy App:');
272
		foreach ($this->getInstances() as $instanceId) {
273
			$this->occ($instanceId, 'app:disable password_policy', false, false);
274
			$this->pm($instanceId);
275
		}
276
		$this->r();
277
278
		foreach ($this->getInstances() as $instance) {
279
			$this->p('Creating users on ' . $instance);
280
			foreach ($this->getConfigArray($instance, 'users') as $userId) {
281
				$this->pm($userId);
282
				$this->occ(
283
					$instance, 'user:add --password-from-env ' . $userId, false, false,
284
					['OC_PASS' => 'testtest']
285
				);
286
			}
287
			$this->r();
288
289
			foreach ($this->getConfigArray($instance, 'groups') as $groupId => $users) {
290
				$this->p('Creating group <info>' . $groupId . '</info> on <info>' . $instance . '</info>');
291
				$this->occ($instance, 'group:add ' . $groupId, false, false);
292
				foreach ($users as $userId) {
293
					$this->pm($userId);
294
					$this->occ($instance, 'group:adduser ' . $groupId . ' ' . $userId, true, false);
295
				}
296
				$this->r();
297
			}
298
299
		}
300
	}
301
302
303
	/**
304
	 * @throws ItemNotFoundException
305
	 */
306
	private function reloadCirclesApp() {
307
		$this->p('Reload Circles App');
308
		foreach ($this->getInstances(false) as $instance) {
309
			$this->pm($instance);
310
			$this->occ($instance, 'circles:clean --uninstall', false, false);
311
			$this->occ($instance, 'app:enable circles', true, false);
312
		}
313
		$this->r();
314
315
		$this->p('Empty Circles database on local');
316
		$this->coreQueryBuilder->cleanDatabase();
317
		$this->r();
318
	}
319
320
321
	/**
322
	 * @throws ItemNotFoundException
323
	 */
324
	private function configureCirclesApp() {
325
		$this->p('Configure Circles App');
326
		foreach ($this->getInstances(true) as $instance) {
327
			$this->pm($instance);
328
			foreach ($this->getConfigArray($instance, 'config') as $k => $v) {
329
				$this->occ($instance, 'config:app:set --value ' . $v . ' circles ' . $k, true, false);
330
			}
331
		}
332
		$this->r();
333
	}
334
335
336
	/**
337
	 * @throws ItemNotFoundException
338
	 * @throws Exception
339
	 */
340
	private function confirmVersion() {
341
		$version = $this->configService->getAppValue('installed_version');
342
		$this->p('Confirming version <info>' . $version . '</info>');
343
		foreach ($this->getInstances(false) as $instance) {
344
			$this->pm($instance);
345
			$capabilities = $this->occ($instance, 'circles:check --capabilities');
346
			$v = $this->get('version', $capabilities);
347
			if ($v !== $version) {
348
				throw new Exception($v);
349
			}
350
		}
351
		$this->r();
352
	}
353
354
355
	/**
356
	 * @throws ItemNotFoundException
357
	 * @throws Exception
358
	 */
359
	private function confirmEmptyCircles() {
360
		$this->p('Confirming empty database');
361
		foreach ($this->getInstances() as $instance) {
362
			$this->pm($instance);
363
			$result = $this->occ($instance, 'circles:manage:list --all');
364
			if (!is_array($result) || !empty($result)) {
365
				throw new Exception('no');
366
			}
367
		}
368
		$this->r();
369
	}
370
371
372
	/**
373
	 * @throws ItemNotFoundException
374
	 * @throws Exception
375
	 */
376
	private function syncCircles() {
377
		$this->p('Running Circles Sync');
378
		foreach ($this->getInstances() as $instance) {
379
			$this->pm($instance);
380
			$this->occ($instance, 'circles:sync');
381
		}
382
		$this->r();
383
	}
384
385
386
	/**
387
	 * @throws CircleNotFoundException
388
	 * @throws InvalidItemException
389
	 * @throws ItemNotFoundException
390
	 */
391
	private function statusFreshInstances() {
392
		foreach ($this->getInstances() as $instanceId) {
393
			$this->p('Circles on ' . $instanceId);
394
			$result = $this->occ($instanceId, 'circles:manage:list --all');
395
			$expectedSize = sizeof($this->getConfigArray($instanceId, 'groups'))
396
							+ sizeof($this->getConfigArray($instanceId, 'users'))
397
							+ 1;
398
			$this->r((sizeof($result) === $expectedSize), sizeof($result) . ' circles');
399
400
			$membersList = $groupsList = [];
401
			foreach ($result as $item) {
0 ignored issues
show
Bug introduced by
The expression $result of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
402
				/** @var Circle $circle */
403
				$circle = $this->deserialize($item, Circle::class);
404
405
				if ($circle->isConfig(Circle::CFG_SINGLE)) {
406
					$membersList[] = $circle;
407
				}
408
409
				if ($circle->getSource() === Member::TYPE_GROUP) {
410
					$groupsList[] = $circle;
411
				}
412
			}
413
414
			$instance = $this->getConfig($instanceId, 'config.frontal_cloud_id');
415
416
			foreach ($this->getConfigArray($instanceId, 'users') as $userId) {
417
				$this->p('Checking Single Circle for <comment>' . $userId . '@' . $instance . '</comment>');
418
				$circle = $this->getSingleCircleForMember($membersList, $userId, $instance);
419
420
				$compareToOwnerBasedOn = new Circle();
421
				$compareToOwnerBasedOn->setConfig(Circle::CFG_SINGLE)
422
									  ->setName('user:' . $userId . ':{CIRCLEID}')
423
									  ->setDisplayName($userId);
424
425
				$compareToOwner = new Member();
426
				$compareToOwner->setUserId($userId)
427
							   ->setUserType(Member::TYPE_USER)
428
							   ->setInstance($instance)
429
							   ->setDisplayName($userId)
430
							   ->setId('{CIRCLEID}')
431
							   ->setCircleId('{CIRCLEID}')
432
							   ->setSingleId('{CIRCLEID}')
433
							   ->setStatus(Member::STATUS_MEMBER)
434
							   ->setLevel(Member::LEVEL_OWNER)
435
							   ->setBasedOn($compareToOwnerBasedOn);
436
437
				$compareTo = new Circle();
438
				$compareTo->setOwner($compareToOwner)
439
						  ->setConfig(Circle::CFG_SINGLE)
440
						  ->setName('user:' . $userId . ':{CIRCLEID}')
441
						  ->setDisplayName($userId);
442
443
				$this->confirmCircleData($circle, $compareTo);
444
				$this->r(true, $circle->getSingleId());
445
			}
446
447
			$this->p('Checking Single Circle for <comment>Circles App</comment>');
448
			$circle = $this->getSingleCircleForMember($membersList, 'circles', $instance);
449
450
			$compareToOwnerBasedOn = new Circle();
451
			$compareToOwnerBasedOn->setConfig(Circle::CFG_SINGLE | Circle::CFG_ROOT)
452
								  ->setName('app:circles:{CIRCLEID}')
453
								  ->setDisplayName('circles');
454
455
			$compareToOwner = new Member();
456
			$compareToOwner->setUserId(Application::APP_ID)
457
						   ->setUserType(Member::TYPE_APP)
458
						   ->setInstance($instance)
459
						   ->setDisplayName(Application::APP_ID)
460
						   ->setId('{CIRCLEID}')
461
						   ->setCircleId('{CIRCLEID}')
462
						   ->setSingleId('{CIRCLEID}')
463
						   ->setStatus(Member::STATUS_MEMBER)
464
						   ->setLevel(Member::LEVEL_OWNER)
465
						   ->setBasedOn($compareToOwnerBasedOn);
466
467
			$compareTo = new Circle();
468
			$compareTo->setOwner($compareToOwner)
469
					  ->setConfig(Circle::CFG_SINGLE | Circle::CFG_ROOT)
470
					  ->setName('app:circles:{CIRCLEID}')
471
					  ->setDisplayName('circles');
472
473
			$this->confirmCircleData($circle, $compareTo);
474
			$this->r(true, $circle->getSingleId());
475
476
			foreach ($this->getConfigArray($instanceId, 'groups') as $groupId => $members) {
477
				$this->p('Checking Circle for <comment>' . $groupId . '@' . $instance . '</comment>');
478
				$circle = $this->getCircleFromList($groupsList, 'group:' . $groupId);
479
480
				$appCircle = $this->getSingleCircleForMember($membersList, 'circles', $instance);
481
				$appOwner = $appCircle->getOwner();
482
483
				$compareToOwnerBasedOn = new Circle();
484
				$compareToOwnerBasedOn->setConfig(Circle::CFG_SINGLE | Circle::CFG_ROOT)
485
									  ->setName($appCircle->getName())
486
									  ->setDisplayName($appCircle->getDisplayName());
487
488
				$compareToOwner = new Member();
489
				$compareToOwner->setUserId($appOwner->getUserId())
490
							   ->setUserType($appOwner->getUserType())
491
							   ->setInstance($appOwner->getInstance())
492
							   ->setDisplayName($appOwner->getDisplayName())
493
							   ->setCircleId('{CIRCLEID}')
494
							   ->setSingleId($appOwner->getSingleId())
495
							   ->setStatus($appOwner->getStatus())
496
							   ->setLevel($appOwner->getLevel())
497
							   ->setBasedOn($compareToOwnerBasedOn);
498
499
				$compareTo = new Circle();
500
				$compareTo->setOwner($compareToOwner)
501
						  ->setConfig(Circle::CFG_SYSTEM | Circle::CFG_NO_OWNER | Circle::CFG_HIDDEN)
502
						  ->setName('group:' . $groupId)
503
						  ->setDisplayName($groupId);
504
505
				$this->confirmCircleData($circle, $compareTo);
506
				$this->r(true, $circle->getSingleId());
507
			}
508
509
			$this->output->writeln('');
510
		}
511
	}
512
513
514
	/**
515
	 *
516
	 */
517
	private function createRemoteLink() {
518
		foreach ($this->getInstances() as $instanceId) {
519
			$this->p('Init remote link from ' . $instanceId);
520
			$links = $this->getConfigArray($instanceId, 'remote');
521
			foreach ($links as $link => $type) {
522
				$remote = $this->getConfig($link, 'config.frontal_cloud_id');
523
				$this->pm($remote . '(' . $type . ')');
524
				$this->occ($instanceId, 'circles:remote ' . $remote . ' --type ' . $type . ' --yes');
525
			}
526
			$this->r();
527
		}
528
	}
529
530
531
	/**
532
	 * @throws InvalidItemException
533
	 * @throws ItemNotFoundException
534
	 */
535
	private function buildingLocalDatabase() {
536
		$this->circles = $this->federatedUsers = [];
537
		foreach ($this->getInstances() as $instanceId) {
538
			$this->p('Retrieving Circles from ' . $instanceId);
539
			$circles = $this->occ($instanceId, 'circles:manage:list --all');
540
			foreach ($circles as $item) {
0 ignored issues
show
Bug introduced by
The expression $circles of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
541
				/** @var Circle $circle */
542
				$circle = $this->deserialize($item, Circle::class);
543
				if ($circle->isConfig(Circle::CFG_SINGLE)) {
544
					$pos = strrpos($circle->getName(), ':');
545
					if (!$pos) {
546
						throw new InvalidItemException('could not parse circle.name');
547
					}
548
549
					$owner = new FederatedUser();
550
					$owner->importFromIFederatedUser($circle->getOwner());
551
					$this->federatedUsers[$instanceId][$owner->getUserId()] = $owner;
552
					$this->circles[$instanceId][substr($circle->getName(), 0, $pos)] = $circle;
553
				} else {
554
					$this->circles[$instanceId][$circle->getName()] = $circle;
555
				}
556
			}
557
			$this->r();
558
		}
559
	}
560
561
562
	/**
563
	 * @throws InvalidItemException
564
	 * @throws ItemNotFoundException
565
	 */
566
	private function circleCreation001() {
567
		$this->p('Creating basic circle');
568
569
		$localInstanceId = 'global-scale-1';
570
		$name = self::$TEST_CIRCLES[0];
571
		$owner = $this->getInstanceUsers($localInstanceId)[1];
572
		$dataCreatedCircle001 =
573
			$this->occ($localInstanceId, 'circles:manage:create --type user ' . $owner . ' ' . $name);
574
		/** @var Circle $createdCircle */
575
		$createdCircle = $this->deserialize($dataCreatedCircle001, Circle::class);
576
		$this->circles[$localInstanceId][$createdCircle->getName()] = $createdCircle;
577
		$this->r(true, $createdCircle->getSingleId());;
578
579
		$this->p('Comparing data returned at creation');
580
		if ($createdCircle->getSingleId() === '' || $createdCircle->getOwner()->getId() === '') {
581
			throw new InvalidItemException('empty id or owner.member_id');
582
		}
583
584
		$knownOwner = new Member();
585
		$knownOwner->importFromIFederatedUser($this->federatedUsers[$localInstanceId][$owner]);
586
		$knownOwner->setCircleId($createdCircle->getSingleId());
587
		$knownOwner->setLevel(Member::LEVEL_OWNER);
588
		$knownOwner->setStatus(Member::STATUS_MEMBER);
589
		$knownOwner->setId($createdCircle->getOwner()->getId());
590
591
		$compareTo = new Circle();
592
		$compareTo->setOwner($knownOwner)
593
				  ->setSingleId($createdCircle->getSingleId())
594
				  ->setInitiator($knownOwner)
595
				  ->setConfig(Circle::CFG_CIRCLE)
596
				  ->setName($name)
597
				  ->setDisplayName($name);
598
		echo json_encode($createdCircle, JSON_PRETTY_PRINT);
599
		$this->confirmCircleData($createdCircle, $compareTo, 'circle', true);
600
		$this->r();
601
602
603
		$this->p('Comparing local stored data');
604
		$dataCircle = $this->occ($localInstanceId, 'circle:manage:details ' . $createdCircle->getSingleId());
605
606
		/** @var Circle $tmpCircle */
607
		$tmpCircle = $this->deserialize($dataCircle, Circle::class);
608
		$this->confirmCircleData($tmpCircle, $createdCircle);
609
		$this->r(true, $tmpCircle->getSingleId());
610
611
		$links = $this->getConfigArray('global-scale-1', 'remote');
612
		foreach ($this->getInstances(false) as $instanceId) {
613
			$this->p('Comparing data stored on ' . $instanceId);
614
			$dataCircle =
615
				$this->occ($instanceId, 'circle:manage:details ' . $createdCircle->getSingleId(), false);
616
617
			if ($instanceId === $localInstanceId || $links[$instanceId] === 'GlobalScale') {
618
				/** @var Circle $tmpCircle */
619
				$tmpCircle = $this->deserialize($dataCircle, Circle::class);
620
				// we reset some data that should not be available on remote instance
621
				$createdCircle->setInitiator(null)
622
							  ->getOwner()->setBasedOn(null);
623
				$this->confirmCircleData($tmpCircle, $createdCircle);
624
				$this->r(true, $tmpCircle->getSingleId());
625
			} else {
626
				if (is_null($dataCircle)) {
627
					$this->r(true, 'empty');
628
				} else {
629
					$this->r(false, 'should be empty');
630
				}
631
			}
632
		}
633
	}
634
635
636
	/**
637
	 * @throws InvalidItemException
638
	 * @throws ItemNotFoundException
639
	 * @throws CircleNotFoundException
640
	 */
641
	private function addLocalMemberByUserId() {
642
		$this->p('Adding local user as member, based on userId');
643
644
		$instanceId = 'global-scale-1';
645
		$circleName = self::$TEST_CIRCLES[0];
646
		$member = $this->getInstanceUsers($instanceId)[2];
647
648
		$addedMember = $this->processMemberAdd($instanceId, $circleName, $member, 'user');
649
		$this->r(true, $addedMember->getId());;
650
651
		// check test2
652
	}
653
654
655
	/**
656
	 * @throws CircleNotFoundException
657
	 * @throws InvalidItemException
658
	 * @throws ItemNotFoundException
659
	 */
660
	private function addLocalMemberBySingleId() {
661
		$this->p('Adding local user as member, based on singleId');
662
663
		$localInstanceId = 'global-scale-1';
664
		$name = self::$TEST_CIRCLES[0];
665
		$circle = $this->getCircleByName($localInstanceId, $name);
666
		$userId = $this->getInstanceUsers($localInstanceId)[6];
667
		$userCircle = $this->getCircleByName($localInstanceId, 'user:' . $userId);
668
		$user = $userCircle->getOwner();
669
		$dataAddedMember =
670
			$this->occ(
671
				$localInstanceId, 'circles:members:add ' . $circle->getSingleId() . ' ' . $user->getSingleId()
672
			);
673
		/** @var Member $addedMember */
674
		$addedMember = $this->deserialize($dataAddedMember, Member::class);
675
		$this->r(true, $addedMember->getId());;
676
677
		// check test6
678
	}
679
680
681
	private function addLocalMemberUsingMember() {
682
		$this->p('Adding local member using local Member');
683
		$localInstanceId = 'global-scale-1';
684
		$initiator = $this->getInstanceUsers($localInstanceId)[6];
0 ignored issues
show
Unused Code introduced by
$initiator is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
685
		$member = $this->getInstanceUsers($localInstanceId)[6];
0 ignored issues
show
Unused Code introduced by
$member is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
686
		$circleName = self::$TEST_CIRCLES[0];
687
688
		$circle = $this->getCircleByName($localInstanceId, $circleName);
689
		$userId = $this->getInstanceUsers($localInstanceId)[6];
690
		$userCircle = $this->getCircleByName($localInstanceId, 'user:' . $userId);
691
		$user = $userCircle->getOwner();
692
		$dataAddedMember =
693
			$this->occ(
694
				$localInstanceId, 'circles:members:add ' . $circle->getSingleId() . ' ' . $user->getSingleId()
695
			);
696
		/** @var Member $addedMember */
697
		$addedMember = $this->deserialize($dataAddedMember, Member::class);
698
		$this->r(true, $addedMember->getId());;
699
	}
700
701
	private function levelLocalMemberToModerator() {
702
		$this->p('Changing local level to Moderator');
703
	}
704
705
	private function addLocalMemberUsingModerator() {
706
		$this->p('Adding local member using local Moderator');
707
	}
708
709
	private function levelLocalMemberToModeratorUsingModerator() {
710
		// fail
711
		$this->p('Changing local level to moderator using local Moderator');
712
	}
713
714
	private function addRemoteMemberUsingModerator() {
715
		$this->p('Adding remote user using local Moderator');
716
	}
717
718
	private function addLocalMemberUsingRemoteMember() {
719
		// fail
720
		$this->p('Adding local user using remote Member');
721
	}
722
723
	private function addRemoteMemberUsingRemoteMember() {
724
		// fail
725
		$this->p('Adding remote user using remote Member');
726
	}
727
728
	private function levelRemoteMemberToAdmin() {
729
		$this->p('Changing remote level to Admin');
730
	}
731
732
	private function addLocalMemberUsingRemoteAdmin() {
733
		$this->p('Adding remote member using remote Admin');
734
	}
735
736
	private function addRemoteMemberUsingRemoteAdmin() {
737
		$this->p('Adding remote member using remote Admin');
738
	}
739
740
	private function levelRemoteMemberToModeratorUsingRemoteAdmin() {
741
	}
742
743
	private function levelRemoteMemberToAdminUsingRemoteAdmin() {
744
		// fail
745
	}
746
747
	private function verifyMemberList001() {
748
	}
749
750
	private function removeLocalMemberUsingRemoteMember() {
751
		// fail
752
	}
753
754
	private function removeLocalMemberUsingRemoteAdmin() {
755
	}
756
757
758
	private function removeRemoteMemberUsingRemoteMember() {
759
		// fail
760
	}
761
762
	private function removeRemoteMemberUsingRemoteAdmin() {
763
	}
764
765
	/**
766
	 * @param Circle $circle
767
	 * @param Circle $compareTo
768
	 * @param string $prefix
769
	 * @param bool $versa
770
	 * @param array $params
771
	 *
772
	 * @throws Exception
773
	 */
774
	private function confirmCircleData(
775
		Circle $circle,
776
		Circle $compareTo,
777
		string $prefix = 'circle',
778
		bool $versa = false,
779
		array $params = []
780
	) {
781
782
		if (empty($params)) {
783
			$params = [
784
				'CIRCLEID' => $circle->getSingleId()
785
			];
786
		}
787
788
		$this->compare($compareTo->getSingleId(), $circle->getSingleId(), $prefix . '.id', $params);
789
		$this->compare($compareTo->getName(), $circle->getName(), $prefix . '.name', $params);
790
		$this->compare(
791
			$compareTo->getDisplayName(), $circle->getDisplayName(), $prefix . '.displayName', $params
792
		);
793
		$this->compareInt($compareTo->getConfig(), $circle->getConfig(), $prefix . '.config', true);
794
		$this->compareInt($compareTo->getSource(), $circle->getSource(), $prefix . '.source');
795
796
		if ($compareTo->hasOwner()) {
797
			$compareToOwner = $compareTo->getOwner();
798
			if ($compareToOwner !== null) {
799
				$owner = $circle->getOwner();
800
				if ($owner === null) {
801
					throw new Exception('empty owner');
802
				}
803
				if ($owner->getCircleId() !== $circle->getSingleId()) {
804
					throw new Exception($prefix . '.owner.circleId is different than ' . $prefix . '.id');
805
				}
806
				$this->confirmMemberData($owner, $compareToOwner, 'owner', false, $params);
807
			}
808
		}
809
		if ($compareTo->hasInitiator()) {
810
			$compareToInitiator = $compareTo->getInitiator();
811
			if ($compareToInitiator !== null) {
812
				if (!$circle->hasInitiator()) {
813
					throw new Exception('empty initiator');
814
				}
815
				$initiator = $circle->getInitiator();
816
				if ($initiator->getCircleId() !== $circle->getSingleId()) {
817
					throw new Exception($prefix . '.initiator.circleId is different than ' . $prefix . '.id');
818
				}
819
				$this->confirmMemberData($initiator, $compareToInitiator, 'owner', false, $params);
820
			}
821
		}
822
823
		if ($versa) {
824
			$this->confirmCircleData($compareTo, $circle);
825
		}
826
	}
827
828
	/**
829
	 * @param Member $member
830
	 * @param Member $compareTo
831
	 * @param string $prefix
832
	 * @param bool $versa
833
	 * @param array $params
834
	 *
835
	 * @throws Exception
836
	 */
837
	private function confirmMemberData(
838
		Member $member,
839
		Member $compareTo,
840
		string $prefix = 'member',
841
		bool $versa = false,
842
		array $params = []
843
	) {
844
		$this->compare($compareTo->getId(), $member->getId(), $prefix . '.id', $params);
845
		$this->compare($compareTo->getCircleId(), $member->getCircleId(), $prefix . '.circleId', $params);
846
		$this->compare($compareTo->getSingleId(), $member->getSingleId(), $prefix . '.singleId', $params);
847
		$this->compare($compareTo->getUserId(), $member->getUserId(), $prefix . '.userId', $params);
848
		$this->compare(
849
			$compareTo->getDisplayName(), $member->getDisplayName(), $prefix . '.displayName', $params
850
		);
851
		$this->compareInt($compareTo->getUserType(), $member->getUserType(), $prefix . '.userType');
852
		$this->compare($compareTo->getInstance(), $member->getInstance(), $prefix . '.instance', $params);
853
		$this->compareInt($compareTo->getLevel(), $member->getLevel(), $prefix . '.level', true);
854
		$this->compare($compareTo->getStatus(), $member->getStatus(), $prefix . '.status', $params);
855
856
		$compareToBasedOn = $compareTo->getBasedOn();
857
		if ($compareToBasedOn !== null) {
858
			$basedOn = $member->getBasedOn();
859
			if ($basedOn === null) {
860
				throw new Exception('empty ' . $prefix . '.basedOn');
861
			}
862
			$this->confirmCircleData($basedOn, $compareToBasedOn, $prefix . '.basedOn', false, $params);
863
		}
864
865
	}
866
867
868
	/**
869
	 * @param string $expected
870
	 * @param string $compare
871
	 * @param string $def
872
	 * @param array $params
873
	 *
874
	 * @throws Exception
875
	 */
876
	private function compare(string $expected, string $compare, string $def, array $params) {
877
		if ($expected !== ''
878
			&& $this->feedStringWithParams($expected, $params) !== $compare) {
879
			throw new Exception($def . ': ' . $compare . ' (' . $expected . ')');
880
		}
881
	}
882
883
	/**
884
	 * @param int $expected
885
	 * @param int $compare
886
	 * @param string $def
887
	 * @param array $params
0 ignored issues
show
Bug introduced by
There is no parameter named $params. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
888
	 * @param bool $force
889
	 *
890
	 * @throws Exception
891
	 */
892
	private function compareInt(int $expected, int $compare, string $def, bool $force = false) {
893
		if (($expected > 0 || ($force && $expected >= 0))
894
			&& $expected !== $compare) {
895
			throw new Exception('wrong ' . $def . ': ' . $compare . ' (' . $expected . ')');
896
		}
897
	}
898
899
900
	/**
901
	 * @param bool $localIncluded
902
	 *
903
	 * @return array
904
	 */
905
	private function getInstances(bool $localIncluded = true): array {
906
		$instances = self::$INSTANCES;
907
		if (!$localIncluded) {
908
			$instances = array_diff($instances, [$this->local]);
909
		}
910
911
		return $instances;
912
	}
913
914
915
	/**
916
	 * @param array $circles
917
	 * @param string $userId
918
	 * @param string $instance
919
	 *
920
	 * @return Circle
921
	 * @throws CircleNotFoundException
922
	 */
923
	private function getSingleCircleForMember(array $circles, string $userId, string $instance): Circle {
924
		foreach ($circles as $circle) {
925
			$owner = $circle->getOwner();
926
			if ($owner->getUserId() === $userId && $owner->getInstance() === $instance) {
927
				return $circle;
928
			}
929
		}
930
931
		throw new CircleNotFoundException('cannot find ' . $userId . ' in the list of Single Circle');
932
	}
933
934
935
	/**
936
	 * @param string $instanceId
937
	 * @param string $name
938
	 *
939
	 * @return Circle
940
	 * @throws CircleNotFoundException
941
	 */
942
	private function getCircleByName(string $instanceId, string $name): Circle {
943
		if (array_key_exists($instanceId, $this->circles)
944
			&& array_key_exists($name, $this->circles[$instanceId])) {
945
			return $this->circles[$instanceId][$name];
946
		}
947
948
		throw new CircleNotFoundException(
949
			'cannot extract \'' . $name . '\' from the list of generated Circles'
950
		);
951
	}
952
953
954
	/**
955
	 * @param array $circles
956
	 * @param string $name
957
	 *
958
	 * @return Circle
959
	 * @throws CircleNotFoundException
960
	 */
961
	private function getCircleFromList(array $circles, string $name): Circle {
962
		foreach ($circles as $circle) {
963
			if ($circle->getName() === $name) {
964
				return $circle;
965
			}
966
		}
967
968
		throw new CircleNotFoundException(
969
			'cannot extract  \'' . $name . '\' from the list of provided Circles'
970
		);
971
	}
972
973
974
	/**
975
	 * @param string $instance
976
	 * @param string $key
977
	 *
978
	 * @return string
979
	 * @throws ItemNotFoundException
980
	 */
981
	private function getConfig(string $instance, string $key): string {
982
		$config = $this->getConfigInstance($instance);
983
984
		return $this->get($key, $config);
985
	}
986
987
	/**
988
	 * @param string $instance
989
	 * @param string $key
990
	 *
991
	 * @return array
992
	 * @throws ItemNotFoundException
993
	 */
994
	private function getConfigArray(string $instance, string $key): array {
995
		$config = $this->getConfigInstance($instance);
996
997
		return $this->getArray($key, $config);
998
	}
999
1000
1001
	/**
1002
	 * @param string $instance
1003
	 *
1004
	 * @return array
1005
	 * @throws ItemNotFoundException
1006
	 */
1007
	private function getConfigInstance(string $instance): array {
1008
		foreach ($this->getArray('instances', $this->config) as $item) {
1009
			if (strtolower($this->get('id', $item)) === strtolower($instance)) {
1010
				return $item;
1011
			}
1012
		}
1013
1014
		throw new ItemNotFoundException($instance . ' not found');
1015
	}
1016
1017
1018
	/**
1019
	 * @param $instanceId
1020
	 *
1021
	 * @return array
1022
	 * @throws ItemNotFoundException
1023
	 */
1024
	private function getInstanceUsers($instanceId): array {
1025
		return $this->getConfigArray($instanceId, 'users');
1026
	}
1027
1028
1029
	/**
1030
	 * @param string $instance
1031
	 * @param string $cmd
1032
	 * @param bool $exceptionOnFail
1033
	 * @param bool $jsonAsOutput
1034
	 * @param array $env
1035
	 *
1036
	 * @return array
1037
	 * @throws ItemNotFoundException
1038
	 * @throws Exception
1039
	 */
1040
	private function occ(
1041
		string $instance,
1042
		string $cmd,
1043
		bool $exceptionOnFail = true,
1044
		bool $jsonAsOutput = true,
1045
		array $env = []
1046
	): ?array {
1047
		$configInstance = $this->getConfigInstance($instance);
1048
		$path = $this->get('path', $configInstance);
1049
		$occ = rtrim($path, '/') . '/occ';
1050
1051
		$command = array_merge([$occ], explode(' ', $cmd));
1052
		if ($jsonAsOutput) {
1053
			$command = array_merge($command, ['--output=json']);
1054
		}
1055
		$process = new Process($command);
1056
		$process->run(null, $env);
1057
1058
		if ($exceptionOnFail && !$process->isSuccessful()) {
1059
			throw new Exception(implode(' ', $command) . ' failed');
1060
		}
1061
1062
		$output = json_decode($process->getOutput(), true);
1063
		if (!is_array($output)) {
1064
			return null;
1065
		}
1066
1067
		return $output;
1068
	}
1069
1070
1071
1072
	//
1073
	//
1074
	//
1075
1076
1077
	/**
1078
	 * @param string $title
1079
	 */
1080
	private function t(string $title): void {
1081
		$this->output->writeln('');
1082
		$this->output->writeln('<comment>### ' . $title . '</comment>');
1083
		$this->output->writeln('');
1084
	}
1085
1086
	/**
1087
	 * @param string $processing
1088
	 */
1089
	private function p(string $processing): void {
1090
		$this->pOn = true;
1091
		$this->output->write('- ' . $processing . ': ');
1092
	}
1093
1094
	/**
1095
	 * @param string $more
1096
	 */
1097
	private function pm(string $more): void {
1098
		$this->output->write($more . ' ');
1099
	}
1100
1101
	/**
1102
	 * @param bool $result
1103
	 * @param string $info
1104
	 */
1105
	private function r(bool $result = true, string $info = ''): void {
1106
		$this->pOn = false;
1107
		if ($result) {
1108
			$this->output->writeln('<info>' . (($info !== '') ? $info : 'done') . '</info>');
1109
		} else {
1110
			$this->output->writeln('<error>' . (($info !== '') ? $info : 'done') . '</error>');
1111
		}
1112
	}
1113
1114
1115
	/**
1116
	 * @param string $instanceId
1117
	 * @param string $circleName
1118
	 * @param string $userId
1119
	 * @param string $type
1120
	 *
1121
	 * @return Member
1122
	 * @throws CircleNotFoundException
1123
	 * @throws InvalidItemException
1124
	 * @throws ItemNotFoundException
1125
	 */
1126
	private function processMemberAdd(string $instanceId, string $circleName, string $userId, string $type
1127
	): Member {
1128
		$circle = $this->getCircleByName($instanceId, $circleName);
1129
		$dataAddedMember =
1130
			$this->occ(
1131
				$instanceId,
1132
				'circles:members:add ' . $circle->getSingleId() . ' ' . $userId . ' --type ' . $type
1133
			);
1134
		/** @var Member $addedMember */
1135
		$addedMember = $this->deserialize($dataAddedMember, Member::class);
1136
1137
1138
		echo 'ADDEDMEMBER: ' . json_encode($addedMember, JSON_PRETTY_PRINT) . "\n";
1139
1140
		$federatedUser = $this->federatedUsers[$instanceId][$userId];
1141
		echo 'FEDERATEDUER: ' . json_encode($federatedUser, JSON_PRETTY_PRINT) . "\n";
1142
1143
		return $addedMember;
1144
	}
1145
1146
}
1147
1148