Completed
Push — update/nav-unification-redirec... ( 35fb11...9c8ba6 )
by
unknown
90:40 queued 80:07
created

AddCommandTest::testGetDefaultFilename()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 35

Duplication

Lines 6
Ratio 17.14 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 6
loc 35
rs 9.36
c 0
b 0
f 0
1
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
/**
3
 * Tests for the changelogger add command.
4
 *
5
 * @package automattic/jetpack-changelogger
6
 */
7
8
// phpcs:disable WordPress.WP.AlternativeFunctions, WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
9
10
namespace Automattic\Jetpack\Changelogger\Tests;
11
12
use Automattic\Jetpack\Changelogger\Utils;
13
use Symfony\Component\Console\Helper\DebugFormatterHelper;
14
use Symfony\Component\Console\Output\ConsoleOutput;
15
use Symfony\Component\Console\Output\NullOutput;
16
use Wikimedia\TestingAccessWrapper;
17
18
/**
19
 * Tests for the changelogger add command.
20
 *
21
 * @covers \Automattic\Jetpack\Changelogger\AddCommand
22
 */
23
class AddCommandTest extends CommandTestCase {
24
	use \Yoast\PHPUnitPolyfills\Polyfills\AssertionRenames;
25
26
	/**
27
	 * Set up.
28
	 *
29
	 * @before
30
	 */
31
	public function set_up() {
32
		parent::set_up();
33
		$this->useTempDir();
34
	}
35
36
	/**
37
	 * Test getDefaultFilename().
38
	 */
39
	public function testGetDefaultFilename() {
40 View Code Duplication
		if ( in_array( '--debug', $GLOBALS['argv'], true ) ) {
41
			$output = new ConsoleOutput();
42
			$output->setVerbosity( ConsoleOutput::VERBOSITY_DEBUG );
43
		} else {
44
			$output = new NullOutput();
45
		}
46
		$w = TestingAccessWrapper::newFromObject( $this->getCommand( 'add' ) );
47
48
		// Test with no git checkout.
49
		$this->assertMatchesRegularExpression( '/^\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}-\d{6}$/', $w->getDefaultFilename( $output ) );
50
51
		// Create a git checkout, master branch.
52
		$args = array(
53
			$output,
54
			new DebugFormatterHelper(),
55
			array(
56
				'mustRun' => true,
57
				'env'     => array(
58
					'GIT_AUTHOR_NAME'     => 'Dummy',
59
					'GIT_AUTHOR_EMAIL'    => '[email protected]',
60
					'GIT_COMMITTER_NAME'  => 'Dummy',
61
					'GIT_COMMITTER_EMAIL' => '[email protected]',
62
				),
63
			),
64
		);
65
		Utils::runCommand( array( 'git', 'init', '.' ), ...$args );
0 ignored issues
show
Bug introduced by
The call to runCommand() misses a required argument $formatter.

This check looks for function calls that miss required arguments.

Loading history...
Documentation introduced by
$args is of type array<integer,object<Sym...\\\"string\\\"}>\"}>"}>, but the function expects a object<Symfony\Component...Output\OutputInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
66
		Utils::runCommand( array( 'git', 'checkout', '-b', 'master' ), ...$args );
0 ignored issues
show
Documentation introduced by
$args is of type array<integer,object<Sym...\\\"string\\\"}>\"}>"}>, but the function expects a object<Symfony\Component...Output\OutputInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
The call to runCommand() misses a required argument $formatter.

This check looks for function calls that miss required arguments.

Loading history...
67
		Utils::runCommand( array( 'git', 'commit', '--allow-empty', '-m', 'Empty' ), ...$args );
0 ignored issues
show
Documentation introduced by
$args is of type array<integer,object<Sym...\\\"string\\\"}>\"}>"}>, but the function expects a object<Symfony\Component...Output\OutputInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
The call to runCommand() misses a required argument $formatter.

This check looks for function calls that miss required arguments.

Loading history...
68
		$this->assertMatchesRegularExpression( '/^\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}-\d{6}$/', $w->getDefaultFilename( $output ) );
69
70
		// Try a named branch.
71
		Utils::runCommand( array( 'git', 'checkout', '-b', 'test/default-filename' ), ...$args );
0 ignored issues
show
Documentation introduced by
$args is of type array<integer,object<Sym...\\\"string\\\"}>\"}>"}>, but the function expects a object<Symfony\Component...Output\OutputInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
The call to runCommand() misses a required argument $formatter.

This check looks for function calls that miss required arguments.

Loading history...
72
		$this->assertSame( 'test-default-filename', $w->getDefaultFilename( $output ) );
73
	}
74
75
	/**
76
	 * Test mkdir failure in execute().
77
	 */
78
	public function testExecute_mkdirFail() {
79
		file_put_contents( 'changelog', '' );
80
		$tester = $this->getTester( 'add' );
81
		$code   = $tester->execute( array() );
82
		$this->assertSame( 1, $code );
83
		$this->assertMatchesRegularExpression( '{^Could not create directory /.*/phpunit-changelogger-[0-9a-f]{6}/changelog: mkdir\(\): File exists\n$}', $tester->getDisplay() );
84
	}
85
86
	/**
87
	 * Test failure in execute() when file exists.
88
	 */
89
	public function testExecute_fileExists() {
90
		mkdir( 'changelog' );
91
		file_put_contents( 'changelog/testing', '' );
92
		$tester = $this->getTester( 'add' );
93
		$code   = $tester->execute( array( '--filename' => 'testing' ), array( 'interactive' => false ) );
94
		$this->assertSame( 1, $code );
95
		$this->assertMatchesRegularExpression( '{^File "/.*/phpunit-changelogger-[0-9a-f]{6}/changelog/testing" already exists. If you want to replace it, delete it manually.\n$}', $tester->getDisplay() );
96
	}
97
98
	/**
99
	 * Test the try-harder logic for making a filename.
100
	 */
101
	public function testExecute_autoSuffix() {
102
		mkdir( 'changelog' );
103
		$tester = $this->getTester( 'add' );
104
105
		// Create all the files first.
106
		for ( $i = 1; $i < 10; $i++ ) {
107
			$code = $tester->execute(
108
				array(
109
					'--filename'             => 'testing',
110
					'--filename-auto-suffix' => true,
111
					'--significance'         => 'patch',
112
					'--type'                 => 'fixed',
113
					'--entry'                => "Testing $i.",
114
				),
115
				array( 'interactive' => false )
116
			);
117
			$this->assertSame( 0, $code );
118
		}
119
120
		// Then validate them all.
121
		for ( $i = 1; $i < 10; $i++ ) {
122
			$file = 1 === $i ? 'changelog/testing' : "changelog/testing#$i";
123
			$this->assertFileExists( $file );
124
			$this->assertSame(
125
				"Significance: patch\nType: fixed\n\nTesting $i.\n",
126
				file_get_contents( $file )
127
			);
128
		}
129
	}
130
131
	/**
132
	 * Test the command.
133
	 *
134
	 * @dataProvider provideExecute
135
	 * @param string[]    $args Command line arguments.
136
	 * @param array       $options Options for the test and CommandTester.
137
	 * @param string[]    $inputs User inputs.
138
	 * @param int         $expectExitCode Expected exit code.
139
	 * @param string|null $expectFile Expected change file contents, or null if no file should exist.
140
	 * @param string[]    $expectOutputRegexes Regexes to run against the output.
141
	 */
142
	public function testExecute( array $args, array $options, array $inputs, $expectExitCode, $expectFile, $expectOutputRegexes = array() ) {
143 View Code Duplication
		if ( isset( $options['composer.json'] ) ) {
144
			file_put_contents( 'composer.json', json_encode( $options['composer.json'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) );
145
			unset( $options['composer.json'] );
146
		}
147
148
		$tester = $this->getTester( 'add' );
149
		$tester->setInputs( $inputs );
150
		$code = $tester->execute( $args, $options );
151
		foreach ( $expectOutputRegexes as $re ) {
152
			$this->assertMatchesRegularExpression( $re, $tester->getDisplay() );
153
		}
154
		$this->assertSame( $expectExitCode, $code );
155
		$this->assertDirectoryExists( './changelog' );
156
		$files = glob( './changelog/*' );
157
		if ( null === $expectFile ) {
158
			$this->assertCount( 0, $files, 'No change file is expected' );
159
		} else {
160
			$this->assertCount( 1, $files, 'A change file is expected' );
161
			$this->assertSame( $expectFile, file_get_contents( $files[0] ) );
162
		}
163
	}
164
165
	/**
166
	 * Data provider for testExecute.
167
	 */
168
	public function provideExecute() {
169
		$composerWithTypes   = array(
170
			'extra' => array(
171
				'changelogger' => array(
172
					'types' => array(
173
						'foo' => 'Foo',
174
						'bar' => 'Bar',
175
					),
176
				),
177
			),
178
		);
179
		$composerWithNoTypes = array(
180
			'extra' => array(
181
				'changelogger' => array(
182
					'types' => (object) array(),
183
				),
184
			),
185
		);
186
187
		return array(
188
			'Normal interactive use'                       => array(
189
				array(),
190
				array(),
191
				array( '', 'patch', 'fixed', 'Testing.' ),
192
				0,
193
				"Significance: patch\nType: fixed\n\nTesting.\n",
194
			),
195
			'Normal interactive use with empty entry'      => array(
196
				array(),
197
				array(),
198
				array( '', 'patch', 'fixed', '', 'Too minor.' ),
199
				0,
200
				"Significance: patch\nType: fixed\nComment: Too minor.\n\n\n",
201
			),
202
			'Normal interactive use with empty entry and no comment' => array(
203
				array(),
204
				array(),
205
				array( '', 'patch', 'fixed', '', '' ),
206
				0,
207
				"Significance: patch\nType: fixed\n\n\n",
208
			),
209
			'Interactive use with command line defaults'   => array(
210
				array(
211
					'--significance' => 'patch',
212
					'--type'         => 'fixed',
213
					'--entry'        => 'Testing.',
214
				),
215
				array(),
216
				array( '', '', '', '' ),
217
				0,
218
				"Significance: patch\nType: fixed\n\nTesting.\n",
219
			),
220
			'Interactive use that runs into some errors'   => array(
221
				array(),
222
				array(),
223
				array(
224
					'<bad filename>',
225
					'bad:filename?',
226
					'bad/|\\filename',
227
					'.bad-ilename',
228
					'',
229
					'extreme',
230
					'minor',
231
					'improved',
232
					'fixed',
233
					'',
234
					'Testing.',
235
				),
236
				0,
237
				"Significance: minor\nType: fixed\n\nTesting.\n",
238
				array(
239
					'/Filename may not contain angle brackets/',
240
					'/Filename may not contain colons or question marks/',
241
					'/Filename may not contain slashes, backslashes, or pipes/',
242
					'/Filename may not begin with a dot/',
243
					'/Value "extreme" is invalid/',
244
					'/Value "improved" is invalid/',
245
					'/An empty changelog entry is only allowed when the significance is "patch"/',
246
				),
247
			),
248
			'Interactive use with custom types'            => array(
249
				array(),
250
				array( 'composer.json' => $composerWithTypes ),
251
				array(
252
					'',
253
					'minor',
254
					'fixed',
255
					'foo',
256
					'Testing.',
257
				),
258
				0,
259
				"Significance: minor\nType: foo\n\nTesting.\n",
260
				array(
261
					'/Value "fixed" is invalid/',
262
				),
263
			),
264
			'Interactive use with no types'                => array(
265
				array(),
266
				array( 'composer.json' => $composerWithNoTypes ),
267
				array(
268
					'',
269
					'minor',
270
					'Testing.',
271
				),
272
				0,
273
				"Significance: minor\n\nTesting.\n",
274
				array(),
275
			),
276
277
			'Normal non-interactive use'                   => array(
278
				array(
279
					'--significance' => 'patch',
280
					'--type'         => 'fixed',
281
					'--entry'        => 'Testing.',
282
				),
283
				array( 'interactive' => false ),
284
				array(),
285
				0,
286
				"Significance: patch\nType: fixed\n\nTesting.\n",
287
			),
288
			'Normal non-interactive use with comment'      => array(
289
				array(
290
					'--significance' => 'patch',
291
					'--type'         => 'fixed',
292
					'--comment'      => 'This is a comment',
293
					'--entry'        => 'Testing.',
294
				),
295
				array( 'interactive' => false ),
296
				array(),
297
				0,
298
				"Significance: patch\nType: fixed\nComment: This is a comment\n\nTesting.\n",
299
			),
300
			'Normal non-interactive use with empty entry'  => array(
301
				array(
302
					'--significance' => 'patch',
303
					'--type'         => 'fixed',
304
					'--entry'        => '',
305
				),
306
				array( 'interactive' => false ),
307
				array(),
308
				0,
309
				"Significance: patch\nType: fixed\n\n\n",
310
			),
311
			'Non-interactive use with empty filename'      => array(
312
				array(
313
					'--filename'     => '',
314
					'--significance' => 'patch',
315
					'--type'         => 'fixed',
316
					'--entry'        => '',
317
				),
318
				array( 'interactive' => false ),
319
				array(),
320
				1,
321
				null,
322
				array( '/Filename may not be empty/' ),
323
			),
324
			'Non-interactive use with dot filename'        => array(
325
				array(
326
					'--filename'     => '.bad',
327
					'--significance' => 'patch',
328
					'--type'         => 'fixed',
329
					'--entry'        => '',
330
				),
331
				array( 'interactive' => false ),
332
				array(),
333
				1,
334
				null,
335
				array( '/Filename may not begin with a dot/' ),
336
			),
337
			'Non-interactive use with missing significance' => array(
338
				array(
339
					'--type'  => 'fixed',
340
					'--entry' => 'Testing.',
341
				),
342
				array( 'interactive' => false ),
343
				array(),
344
				1,
345
				null,
346
				array(
347
					'/Significance must be specified in non-interactive mode/',
348
				),
349
			),
350
			'Non-interactive use with invalid significance' => array(
351
				array(
352
					'--significance' => 'bogus',
353
					'--type'         => 'fixed',
354
					'--entry'        => 'Testing.',
355
				),
356
				array( 'interactive' => false ),
357
				array(),
358
				1,
359
				null,
360
				array(
361
					'/Significance value "bogus" is not valid/',
362
				),
363
			),
364
			'Non-interactive use with missing type'        => array(
365
				array(
366
					'--significance' => 'patch',
367
					'--entry'        => 'Testing.',
368
				),
369
				array( 'interactive' => false ),
370
				array(),
371
				1,
372
				null,
373
				array(
374
					'/Type must be specified in non-interactive mode/',
375
				),
376
			),
377
			'Non-interactive use with invalid type'        => array(
378
				array(
379
					'--significance' => 'patch',
380
					'--type'         => 'bogus',
381
					'--entry'        => 'Testing.',
382
				),
383
				array( 'interactive' => false ),
384
				array(),
385
				1,
386
				null,
387
				array(
388
					'/Type "bogus" is not valid/',
389
				),
390
			),
391
			'Non-interactive use with missing entry'       => array(
392
				array(
393
					'--significance' => 'patch',
394
					'--type'         => 'fixed',
395
				),
396
				array( 'interactive' => false ),
397
				array(),
398
				1,
399
				null,
400
				array(
401
					'/Entry must be specified in non-interactive mode/',
402
				),
403
			),
404
			'Non-interactive use with invalid entry'       => array(
405
				array(
406
					'--significance' => 'minor',
407
					'--type'         => 'fixed',
408
					'--entry'        => '',
409
				),
410
				array( 'interactive' => false ),
411
				array(),
412
				1,
413
				null,
414
				array(
415
					'/An empty changelog entry is only allowed when the significance is "patch"/',
416
				),
417
			),
418
			'Non-interactive use with custom type'         => array(
419
				array(
420
					'--significance' => 'patch',
421
					'--type'         => 'foo',
422
					'--entry'        => 'Testing.',
423
				),
424
				array(
425
					'interactive'   => false,
426
					'composer.json' => $composerWithTypes,
427
				),
428
				array(),
429
				0,
430
				"Significance: patch\nType: foo\n\nTesting.\n",
431
				array(),
432
			),
433
			'Non-interactive use with invalid custom type' => array(
434
				array(
435
					'--significance' => 'patch',
436
					'--type'         => 'fixed',
437
					'--entry'        => 'Testing.',
438
				),
439
				array(
440
					'interactive'   => false,
441
					'composer.json' => $composerWithTypes,
442
				),
443
				array(),
444
				1,
445
				null,
446
				array(
447
					'/Type "fixed" is not valid/',
448
				),
449
			),
450
			'Non-interactive use with no type'             => array(
451
				array(
452
					'--significance' => 'patch',
453
					'--entry'        => 'Testing.',
454
				),
455
				array(
456
					'interactive'   => false,
457
					'composer.json' => $composerWithNoTypes,
458
				),
459
				array(),
460
				0,
461
				"Significance: patch\n\nTesting.\n",
462
				array(),
463
			),
464
			'Non-interactive use with no type (2)'         => array(
465
				array(
466
					'--significance' => 'patch',
467
					'--type'         => 'anything',
468
					'--entry'        => 'Testing.',
469
				),
470
				array(
471
					'interactive'   => false,
472
					'composer.json' => $composerWithNoTypes,
473
				),
474
				array(),
475
				0,
476
				"Significance: patch\n\nTesting.\n",
477
				array(
478
					'/This project does not use types. Do not specify --type./',
479
				),
480
			),
481
		);
482
	}
483
484
}
485