Completed
Push — 3.4 ( 94c3bc...f1edbe )
by Daniel
18s
created

ConfigManifestTest::testEnvVarSetRules()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 0
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
class ConfigManifestTest_ConfigManifestAccess extends SS_ConfigManifest {
4
	public function relativeOrder($a, $b) {
5
		return parent::relativeOrder($a, $b);
6
	}
7
}
8
9
class ConfigManifestTest extends SapphireTest {
10
11
	/**
12
	 * This is a helper method for getting a new manifest
13
	 * @param $name
14
	 * @return any
15
	 */
16
	protected function getConfigFixtureValue($name) {
17
		$manifest = new SS_ConfigManifest(dirname(__FILE__).'/fixtures/configmanifest', true, true);
18
		return $manifest->get('ConfigManifestTest', $name);
19
	}
20
21
	/**
22
	 * This is a helper method for displaying a relevant message about a parsing failure
23
	 */
24
	protected function getParsedAsMessage($path) {
25
		return sprintf('Reference path "%s" failed to parse correctly', $path);
26
	}
27
28
	/**
29
	 * A helper method to return a mock of the cache in order to test expectations and reduce dependency
30
	 * @return Zend_Cache_Core
31
	 */
32
	protected function getCacheMock() {
33
		return $this->getMock(
34
			'Zend_Cache_Core',
35
			array('load', 'save'),
36
			array(),
37
			'',
38
			false
39
		);
40
	}
41
42
	/**
43
	 * A helper method to return a mock of the manifest in order to test expectations and reduce dependency
44
	 * @param $methods
45
	 * @return SS_ConfigManifest
46
	 */
47
	protected function getManifestMock($methods) {
48
		return $this->getMock(
49
			'SS_ConfigManifest',
50
			$methods,
51
			array(), // no constructor arguments
52
			'', // default
53
			false // don't call the constructor
54
		);
55
	}
56
57
	/**
58
	 * Test the caching functionality when we are forcing regeneration
59
	 *
60
	 * 1. Test that regenerate is called in the default case and that cache->load isn't
61
	 * 2. Test that save is called correctly after the regeneration
62
	 */
63
	public function testCachingForceRegeneration() {
64
		// Test that regenerate is called correctly.
65
		$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
66
67
		$manifest->expects($this->once()) // regenerate should be called once
68
			->method('regenerate')
69
			->with($this->equalTo(true)); // includeTests = true
70
71
		// Set up a cache where we expect load to never be called
72
		$cache = $this->getCacheMock();
73
		$cache->expects($this->never())
74
			->method('load');
75
76
		$manifest->expects($this->any())
77
			->method('getCache')
78
			->will($this->returnValue($cache));
79
80
		$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', true, true);
81
82
		// Test that save is called correctly
83
		$manifest = $this->getManifestMock(array('getCache'));
84
85
		$cache = $this->getCacheMock();
86
		$cache->expects($this->atLeastOnce())
87
			->method('save');
88
89
		$manifest->expects($this->any())
90
			->method('getCache')
91
			->will($this->returnValue($cache));
92
93
		$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', true, true);
94
	}
95
96
	/**
97
	 * Test the caching functionality when we are not forcing regeneration
98
	 *
99
	 * 1. Test that load is called
100
	 * 2. Test the regenerate is called when the cache is unprimed
101
	 * 3. Test that when there is a value in the cache regenerate isn't called
102
	 */
103
	public function testCachingNotForceRegeneration() {
104
		// Test that load is called
105
		$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
106
107
		// Load should be called twice
108
		$cache = $this->getCacheMock();
109
		$cache->expects($this->exactly(2))
110
			->method('load');
111
112
		$manifest->expects($this->any())
113
			->method('getCache')
114
			->will($this->returnValue($cache));
115
116
		$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', true, false);
117
118
119
		// Now test that regenerate is called because the cache is unprimed
120
		$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
121
122
		$cache = $this->getCacheMock();
123
		$cache->expects($this->exactly(2))
124
			->method('load')
125
			->will($this->onConsecutiveCalls(false, false));
126
127
		$manifest->expects($this->any())
128
			->method('getCache')
129
			->will($this->returnValue($cache));
130
131
		$manifest->expects($this->once())
132
			->method('regenerate')
133
			->with($this->equalTo(false)); //includeTests = false
134
135
		$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', false, false);
136
137
		// Now test that when there is a value in the cache that regenerate isn't called
138
		$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
139
140
		$cache = $this->getCacheMock();
141
		$cache->expects($this->exactly(2))
142
			->method('load')
143
			->will($this->onConsecutiveCalls(array(), array()));
144
145
		$manifest->expects($this->any())
146
			->method('getCache')
147
			->will($this->returnValue($cache));
148
149
		$manifest->expects($this->never())
150
			->method('regenerate');
151
152
		$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', false, false);
153
	}
154
155
	/**
156
	 * Test cache regeneration if all or some of the cache files are missing
157
	 *
158
	 * 1. Test regeneration if all cache files are missing
159
	 * 2. Test regeneration if 'variant_key_spec' cache file is missing
160
	 * 3. Test regeneration if 'php_config_sources' cache file is missing
161
	 */
162
	public function testAutomaticCacheRegeneration(){
163
		$base = dirname(__FILE__) . '/fixtures/configmanifest';
164
165
		// Test regeneration if all cache files are missing
166
		$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
167
168
		$manifest->expects($this->once())// regenerate should be called once
169
			->method('regenerate')
170
			->with($this->equalTo(false)); // includeTests = false
171
172
		// Set up a cache where we expect load to never be called
173
		$cache = $this->getCacheMock();
174
		$cache->expects($this->exactly(2))
175
			->will($this->returnValue(false))
176
			->method('load');
177
178
		$manifest->expects($this->any())
179
			->method('getCache')
180
			->will($this->returnValue($cache));
181
182
		$manifest->__construct($base);
183
184
		// Test regeneration if 'variant_key_spec' cache file is missing
185
		$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
186
187
		$manifest->expects($this->once())// regenerate should be called once
188
			->method('regenerate')
189
			->with($this->equalTo(false)); // includeTests = false
190
191
192
		$cache = $this->getCacheMock();
193
		$cache->expects($this->exactly(2))
194
			->method('load')
195
			->will($this->returnCallback(function ($parameter) {
196
				if (strpos($parameter, 'variant_key_spec') !== false) {
197
					return false;
198
				}
199
				return array();
200
			}));
201
202
		$manifest->expects($this->any())
203
			->method('getCache')
204
			->will($this->returnValue($cache));
205
206
		$manifest->__construct($base);
207
208
		// Test regeneration if 'php_config_sources' cache file is missing
209
		$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
210
211
		$manifest->expects($this->once())// regenerate should be called once
212
			->method('regenerate')
213
			->with($this->equalTo(false)); // includeTests = false
214
215
		$cache = $this->getCacheMock();
216
		$cache->expects($this->exactly(2))
217
			->method('load')
218
			->will($this->returnCallback(function ($parameter) {
219
				if (strpos($parameter, 'php_config_sources') !== false) {
220
					return false;
221
				}
222
				return array();
223
			}));
224
225
		$manifest->expects($this->any())
226
			->method('getCache')
227
			->will($this->returnValue($cache));
228
229
		$manifest->__construct($base);
230
	}
231
232
	/**
233
	 * This test checks the processing of before and after reference paths (module-name/filename#fragment)
234
	 * This method uses fixture/configmanifest/mysite/_config/addyamlconfigfile.yml as a fixture
235
	 */
236
	public function testAddYAMLConfigFileReferencePathParsing() {
237
		// Use a mock to avoid testing unrelated functionality
238
		$manifest = $this->getManifestMock(array('addModule'));
239
240
		// This tests that the addModule method is called with the correct value
241
		$manifest->expects($this->once())
242
			->method('addModule')
243
			->with($this->equalTo(dirname(__FILE__).'/fixtures/configmanifest/mysite'));
244
245
		// Call the method to be tested
246
		$manifest->addYAMLConfigFile(
247
			'addyamlconfigfile.yml',
248
			dirname(__FILE__).'/fixtures/configmanifest/mysite/_config/addyamlconfigfile.yml',
249
			false
250
		);
251
252
		// There is no getter for yamlConfigFragments
253
		$property = new ReflectionProperty('SS_ConfigManifest', 'yamlConfigFragments');
254
		$property->setAccessible(true);
255
256
		// Get the result back from the parsing
257
		$result = $property->getValue($manifest);
258
259
		$this->assertEquals(
260
			array(
261
				array(
262
					'module' => 'mysite',
263
					'file' => 'testfile',
264
					'name' => 'fragment',
265
				),
266
			),
267
			@$result[0]['after'],
268
			$this->getParsedAsMessage('mysite/testfile#fragment')
269
		);
270
271
		$this->assertEquals(
272
			array(
273
				array(
274
					'module' => 'test-module',
275
					'file' => 'testfile',
276
					'name' => 'fragment',
277
				),
278
			),
279
			@$result[1]['after'],
280
			$this->getParsedAsMessage('test-module/testfile#fragment')
281
		);
282
283
		$this->assertEquals(
284
			array(
285
				array(
286
					'module' => '*',
287
					'file' => '*',
288
					'name' => '*',
289
				),
290
			),
291
			@$result[2]['after'],
292
			$this->getParsedAsMessage('*')
293
		);
294
295
		$this->assertEquals(
296
			array(
297
				array(
298
					'module' => '*',
299
					'file' => 'testfile',
300
					'name' => '*'
301
				),
302
			),
303
			@$result[3]['after'],
304
			$this->getParsedAsMessage('*/testfile')
305
		);
306
307
		$this->assertEquals(
308
			array(
309
				array(
310
					'module' => '*',
311
					'file' => '*',
312
					'name' => 'fragment'
313
				),
314
			),
315
			@$result[4]['after'],
316
			$this->getParsedAsMessage('*/*#fragment')
317
		);
318
319
		$this->assertEquals(
320
			array(
321
				array(
322
					'module' => '*',
323
					'file' => '*',
324
					'name' => 'fragment'
325
				),
326
			),
327
			@$result[5]['after'],
328
			$this->getParsedAsMessage('#fragment')
329
		);
330
331
		$this->assertEquals(
332
			array(
333
				array(
334
					'module' => 'test-module',
335
					'file' => '*',
336
					'name' => 'fragment'
337
				),
338
			),
339
			@$result[6]['after'],
340
			$this->getParsedAsMessage('test-module#fragment')
341
		);
342
343
		$this->assertEquals(
344
			array(
345
				array(
346
					'module' => 'test-module',
347
					'file' => '*',
348
					'name' => '*'
349
				),
350
			),
351
			@$result[7]['after'],
352
			$this->getParsedAsMessage('test-module')
353
		);
354
355
		$this->assertEquals(
356
			array(
357
				array(
358
					'module' => 'test-module',
359
					'file' => '*',
360
					'name' => '*'
361
				),
362
			),
363
			@$result[8]['after'],
364
			$this->getParsedAsMessage('test-module/*')
365
		);
366
367
		$this->assertEquals(
368
			array(
369
				array(
370
					'module' => 'test-module',
371
					'file' => '*',
372
					'name' => '*'
373
				),
374
			),
375
			@$result[9]['after'],
376
			$this->getParsedAsMessage('test-module/*/#*')
377
		);
378
	}
379
380
	public function testClassRules() {
381
		$config = $this->getConfigFixtureValue('Class');
382
383
		$this->assertEquals(
384
			'Yes', @$config['DirectorExists'],
385
			'Only rule correctly detects existing class'
386
		);
387
388
		$this->assertEquals(
389
			'No', @$config['NoSuchClassExists'],
390
			'Except rule correctly detects missing class'
391
		);
392
	}
393
394
	public function testModuleRules() {
395
		$config = $this->getConfigFixtureValue('Module');
396
397
		$this->assertEquals(
398
			'Yes', @$config['MysiteExists'],
399
			'Only rule correctly detects existing module'
400
		);
401
402
		$this->assertEquals(
403
			'No', @$config['NoSuchModuleExists'],
404
			'Except rule correctly detects missing module'
405
		);
406
	}
407
408
	public function testEnvVarSetRules() {
0 ignored issues
show
Coding Style introduced by
testEnvVarSetRules uses the super-global variable $_ENV which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
409
		$_ENV['EnvVarSet_Foo'] = 1;
410
		$config = $this->getConfigFixtureValue('EnvVarSet');
411
412
		$this->assertEquals(
413
			'Yes', @$config['FooSet'],
414
			'Only rule correctly detects set environment variable'
415
		);
416
417
		$this->assertEquals(
418
			'No', @$config['BarSet'],
419
			'Except rule correctly detects unset environment variable'
420
		);
421
	}
422
423
	public function testConstantDefinedRules() {
424
		define('ConstantDefined_Foo', 1);
0 ignored issues
show
Coding Style introduced by
This constant is not in uppercase (expected 'CONSTANTDEFINED_FOO').
Loading history...
425
		$config = $this->getConfigFixtureValue('ConstantDefined');
426
427
		$this->assertEquals(
428
			'Yes', @$config['FooDefined'],
429
			'Only rule correctly detects defined constant'
430
		);
431
432
		$this->assertEquals(
433
			'No', @$config['BarDefined'],
434
			'Except rule correctly detects undefined constant'
435
		);
436
	}
437
438
	public function testEnvOrConstantMatchesValueRules() {
0 ignored issues
show
Coding Style introduced by
testEnvOrConstantMatchesValueRules uses the super-global variable $_ENV which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
439
		$_ENV['EnvOrConstantMatchesValue_Foo'] = 'Foo';
440
		define('EnvOrConstantMatchesValue_Bar', 'Bar');
0 ignored issues
show
Coding Style introduced by
This constant is not in uppercase (expected 'ENVORCONSTANTMATCHESVALUE_BAR').
Loading history...
441
		$config = $this->getConfigFixtureValue('EnvOrConstantMatchesValue');
442
443
		$this->assertEquals(
444
			'Yes', @$config['FooIsFoo'],
445
			'Only rule correctly detects environment variable matches specified value'
446
		);
447
448
		$this->assertEquals(
449
			'Yes', @$config['BarIsBar'],
450
			'Only rule correctly detects constant matches specified value'
451
		);
452
453
		$this->assertEquals(
454
			'No', @$config['FooIsQux'],
455
			'Except rule correctly detects environment variable that doesn\'t match specified value'
456
		);
457
458
		$this->assertEquals(
459
			'No', @$config['BarIsQux'],
460
			'Except rule correctly detects environment variable that doesn\'t match specified value'
461
		);
462
463
		$this->assertEquals(
464
			'No', @$config['BazIsBaz'],
465
			'Except rule correctly detects undefined variable'
466
		);
467
	}
468
469
	public function testEnvironmentRules() {
470
		foreach (array('dev', 'test', 'live') as $env) {
471
			Config::nest();
472
473
			Config::inst()->update('Director', 'environment_type', $env);
474
			$config = $this->getConfigFixtureValue('Environment');
475
476
			foreach (array('dev', 'test', 'live') as $check) {
477
				$this->assertEquals(
478
					$env == $check ? $check : 'not'.$check, @$config[ucfirst($check).'Environment'],
479
					'Only & except rules correctly detect environment'
480
				);
481
			}
482
483
			Config::unnest();
484
		}
485
	}
486
487
	public function testDynamicEnvironmentRules() {
488
		// First, make sure environment_type is live
489
		Config::inst()->update('Director', 'environment_type', 'live');
490
		$this->assertEquals('live', Config::inst()->get('Director', 'environment_type'));
491
492
		// Then, load in a new manifest, which includes a _config.php that sets environment_type to dev
493
		$manifest = new SS_ConfigManifest(dirname(__FILE__).'/fixtures/configmanifest_dynamicenv', true, true);
494
		Config::inst()->pushConfigYamlManifest($manifest);
495
496
		// Make sure that stuck
497
		$this->assertEquals('dev', Config::inst()->get('Director', 'environment_type'));
498
499
		// And that the dynamic rule was calculated correctly
500
		$this->assertEquals('dev', Config::inst()->get('ConfigManifestTest', 'DynamicEnvironment'));
501
	}
502
503
	public function testMultipleRules() {
0 ignored issues
show
Coding Style introduced by
testMultipleRules uses the super-global variable $_ENV which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
504
		$_ENV['MultilpleRules_EnvVariableSet'] = 1;
505
		define('MultilpleRules_DefinedConstant', 'defined');
0 ignored issues
show
Coding Style introduced by
This constant is not in uppercase (expected 'MULTILPLERULES_DEFINEDCONSTANT').
Loading history...
506
		$config = $this->getConfigFixtureValue('MultipleRules');
507
508
		$this->assertFalse(
509
			isset($config['TwoOnlyFail']),
510
			'Fragment is not included if one of the Only rules fails.'
511
		);
512
513
		$this->assertTrue(
514
			isset($config['TwoOnlySucceed']),
515
			'Fragment is included if both Only rules succeed.'
516
		);
517
518
		$this->assertTrue(
519
			isset($config['TwoExceptSucceed']),
520
			'Fragment is included if one of the Except rules matches.'
521
		);
522
523
		$this->assertFalse(
524
			isset($config['TwoExceptFail']),
525
			'Fragment is not included if both of the Except rules fail.'
526
		);
527
528
		$this->assertFalse(
529
			isset($config['TwoBlocksFail']),
530
			'Fragment is not included if one block fails.'
531
		);
532
533
		$this->assertTrue(
534
			isset($config['TwoBlocksSucceed']),
535
			'Fragment is included if both blocks succeed.'
536
		);
537
	}
538
539
	public function testRelativeOrder() {
540
		$accessor = new ConfigManifestTest_ConfigManifestAccess(BASE_PATH, true, false);
541
542
		// A fragment with a fully wildcard before rule
543
		$beforeWildcarded = array(
544
			'module' => 'foo', 'file' => 'alpha', 'name' => '1',
545
			'before' => array(array('module' => '*', 'file' => '*', 'name' => '*'))
546
		);
547
		// A fragment with a fully wildcard before rule and a fully explicit after rule
548
		$beforeWildcardedAfterExplicit = array_merge($beforeWildcarded, array(
549
			'after' => array(array('module' => 'bar', 'file' => 'beta', 'name' => '2'))
550
		));
551
		// A fragment with a fully wildcard before rule and two fully explicit after rules
552
		$beforeWildcardedAfterTwoExplicitRules = array_merge($beforeWildcarded, array(
553
			'after' => array(
554
				array('module' => 'bar', 'file' => 'beta', 'name' => '2'),
555
				array('module' => 'baz', 'file' => 'gamma', 'name' => '3')
556
			)
557
		));
558
		// A fragment with a fully wildcard before rule and a partially explicit after rule
559
		$beforeWildcardedAfterPartialWildcarded = array_merge($beforeWildcarded, array(
560
			'after' => array(array('module' => 'bar', 'file' => 'beta', 'name' => '*'))
561
		));
562
563
		// Wildcard should match any module
564
		$this->assertEquals($accessor->relativeOrder(
565
			$beforeWildcarded,
566
			array('module' => 'qux', 'file' => 'delta', 'name' => '4')
567
		), 'before');
568
569
		// Wildcard should match any module even if there is an opposing rule, if opposing rule doesn't match
570
		$this->assertEquals($accessor->relativeOrder(
571
			$beforeWildcardedAfterExplicit,
572
			array('module' => 'qux', 'file' => 'delta', 'name' => '4')
573
		), 'before');
574
575
		// Wildcard should match any module even if there is an opposing rule, if opposing rule doesn't match, no
576
		// matter how many opposing rules
577
		$this->assertEquals($accessor->relativeOrder(
578
			$beforeWildcardedAfterExplicit,
579
			array('module' => 'qux', 'file' => 'delta', 'name' => '4')
580
		), 'before');
581
582
		// Wildcard should match any module even if there is an opposing rule, if opposing rule doesn't match
583
		// (even if some portions do)
584
		$this->assertEquals($accessor->relativeOrder(
585
			$beforeWildcardedAfterExplicit,
586
			array('module' => 'bar', 'file' => 'beta', 'name' => 'nomatchy')
587
		), 'before');
588
589
		// When opposing rule matches, wildcard should be ignored
590
		$this->assertEquals($accessor->relativeOrder(
591
			$beforeWildcardedAfterExplicit,
592
			array('module' => 'bar', 'file' => 'beta', 'name' => '2')
593
		), 'after');
594
595
		// When any one of mutiple opposing rule exists, wildcard should be ignored
596
		$this->assertEquals($accessor->relativeOrder(
597
			$beforeWildcardedAfterTwoExplicitRules,
598
			array('module' => 'bar', 'file' => 'beta', 'name' => '2')
599
		), 'after');
600
601
		$this->assertEquals($accessor->relativeOrder(
602
			$beforeWildcardedAfterTwoExplicitRules,
603
			array('module' => 'baz', 'file' => 'gamma', 'name' => '3')
604
		), 'after');
605
606
		// When two opposed wildcard rules, and more specific one doesn't match, other should win
607
		$this->assertEquals($accessor->relativeOrder(
608
			$beforeWildcardedAfterPartialWildcarded,
609
			array('module' => 'qux', 'file' => 'delta', 'name' => '4')
610
		), 'before');
611
612
		// When two opposed wildcard rules, and more specific one does match, more specific one should win
613
		$this->assertEquals($accessor->relativeOrder(
614
			$beforeWildcardedAfterPartialWildcarded,
615
			array('module' => 'bar', 'file' => 'beta', 'name' => 'wildcardmatchy')
616
		), 'after');
617
	}
618
619
}
620