out = $this->getMockBuilder(ConsoleOutput::class) ->disableOriginalConstructor() ->getMock(); $this->err = $this->getMockBuilder(ConsoleOutput::class) ->disableOriginalConstructor() ->getMock(); $this->in = $this->getMockBuilder(ConsoleInput::class) ->disableOriginalConstructor() ->getMock(); $this->io = new ConsoleIo($this->out, $this->err, $this->in); } /** * teardown method */ public function tearDown(): void { parent::tearDown(); if (is_dir(TMP . 'shell_test')) { $fs = new Filesystem(); $fs->deleteDir(TMP . 'shell_test'); } Log::drop('console-logger'); } /** * Provider for testing choice types. * * @return array */ public static function choiceProvider(): array { return [ [['y', 'n']], ['y,n'], ['y/n'], ['y'], ]; } /** * test ask choices method * * @param array|string $choices */ #[DataProvider('choiceProvider')] public function testAskChoices($choices): void { $this->in->expects($this->once()) ->method('read') ->willReturn('y'); $result = $this->io->askChoice('Just a test?', $choices); $this->assertSame('y', $result); } /** * test ask choices method * * @param array|string $choices */ #[DataProvider('choiceProvider')] public function testAskChoicesInsensitive($choices): void { $this->in->expects($this->once()) ->method('read') ->willReturn('Y'); $result = $this->io->askChoice('Just a test?', $choices); $this->assertSame('Y', $result); } /** * Test ask method */ public function testAsk(): void { $this->out->expects($this->once()) ->method('write') ->with("Just a test?\n> "); $this->in->expects($this->once()) ->method('read') ->willReturn('y'); $result = $this->io->ask('Just a test?'); $this->assertSame('y', $result); } /** * Test ask method */ public function testAskDefaultValue(): void { $this->out->expects($this->once()) ->method('write') ->with("Just a test?\n[n] > "); $this->in->expects($this->once()) ->method('read') ->willReturn(''); $result = $this->io->ask('Just a test?', 'n'); $this->assertSame('n', $result); } /** * testOut method */ public function testOut(): void { $this->out->expects($this->exactly(4)) ->method('write') ->with( ...self::withConsecutive( ['Just a test', 1], [['Just', 'a', 'test'], 1], [['Just', 'a', 'test'], 2], ['', 1] ) ); $this->io->out('Just a test'); $this->io->out(['Just', 'a', 'test']); $this->io->out(['Just', 'a', 'test'], 2); $this->io->out(); } /** * test that verbose and quiet output levels work */ public function testVerboseOut(): void { $this->out->expects($this->exactly(3)) ->method('write') ->with( ...self::withConsecutive( ['Verbose', 1], ['Normal', 1], ['Quiet', 1] ) ); $this->io->level(ConsoleIo::VERBOSE); $this->io->out('Verbose', 1, ConsoleIo::VERBOSE); $this->io->out('Normal', 1, ConsoleIo::NORMAL); $this->io->out('Quiet', 1, ConsoleIo::QUIET); } /** * test that verbose and quiet output levels work */ public function testVerboseOutput(): void { $this->out->expects($this->exactly(3)) ->method('write') ->with( ...self::withConsecutive( ['Verbose', 1], ['Normal', 1], ['Quiet', 1] ) ); $this->io->level(ConsoleIo::VERBOSE); $this->io->verbose('Verbose'); $this->io->out('Normal'); $this->io->quiet('Quiet'); } /** * test that verbose and quiet output levels work */ public function testQuietOutput(): void { $this->out->expects($this->exactly(2)) ->method('write') ->with( ...self::withConsecutive( ['Quiet', 1], ['Quiet', 1] ) ); $this->io->level(ConsoleIo::QUIET); $this->io->out('Verbose', 1, ConsoleIo::VERBOSE); $this->io->out('Normal', 1, ConsoleIo::NORMAL); $this->io->out('Quiet', 1, ConsoleIo::QUIET); $this->io->verbose('Verbose'); $this->io->quiet('Quiet'); } /** * testErr method */ public function testErr(): void { $this->err->expects($this->exactly(4)) ->method('write') ->with( ...self::withConsecutive( ['Just a test', 1], [['Just', 'a', 'test'], 1], [['Just', 'a', 'test'], 2], ['', 1] ) ); $this->io->err('Just a test'); $this->io->err(['Just', 'a', 'test']); $this->io->err(['Just', 'a', 'test'], 2); $this->io->err(); } /** * Tests abort() wrapper. */ public function testAbort(): void { $this->expectException(StopException::class); $this->expectExceptionMessage('Some error'); $this->expectExceptionCode(1); $this->err->expects($this->once()) ->method('write') ->with('Some error', 1); $this->expectException(StopException::class); $this->expectExceptionCode(1); $this->expectExceptionMessage('Some error'); $this->io->abort('Some error'); } /** * Tests abort() wrapper. */ public function testAbortCustomCode(): void { $this->expectException(StopException::class); $this->expectExceptionMessage('Some error'); $this->expectExceptionCode(99); $this->err->expects($this->once()) ->method('write') ->with('Some error', 1); $this->expectException(StopException::class); $this->expectExceptionCode(99); $this->expectExceptionMessage('Some error'); $this->io->abort('Some error', 99); } /** * testNl */ public function testNl(): void { $newLine = "\n"; if (DS === '\\') { $newLine = "\r\n"; } $this->assertSame($this->io->nl(), $newLine); $this->assertSame($this->io->nl(2), $newLine . $newLine); $this->assertSame($this->io->nl(1), $newLine); } /** * testHr */ public function testHr(): void { $bar = str_repeat('-', 79); $this->out->expects($this->exactly(6)) ->method('write') ->with( ...self::withConsecutive( ['', 0], [$bar, 1], ['', 0], ['', true], [$bar, 1], ['', true] ) ); $this->io->hr(); $this->io->hr(2); } /** * Test overwriting. */ public function testOverwrite(): void { $number = strlen('Some text I want to overwrite'); $this->out->expects($this->atLeast(4)) ->method('write') ->with( ...self::withConsecutive( ['Some text I want to overwrite', 0], [str_repeat("\x08", $number), 0], ['Less text', 0], [str_repeat(' ', $number - 9), 0], [PHP_EOL, 0] ) ) ->willReturn( $number, 9, 9, 1, 0 ); $this->io->out('Some text I want to overwrite', 0); $this->io->overwrite('Less text'); } /** * Test overwriting content with shorter content */ public function testOverwriteWithShorterContent(): void { $length = strlen('12345'); $this->out->expects($this->exactly(7)) ->method('write') ->with( ...self::withConsecutive( ['12345'], // Backspaces [str_repeat("\x08", $length), 0], ['123', 0], // 2 spaces output to pad up to 5 bytes [str_repeat(' ', $length - 3), 0], // Backspaces [str_repeat("\x08", $length), 0], ['12', 0], [str_repeat(' ', $length - 2), 0] ) ) ->willReturn( $length, $length, 3, $length - 3, $length, 2, $length - 2 ); $this->io->out('12345'); $this->io->overwrite('123', 0); $this->io->overwrite('12', 0); } /** * Test overwriting content with longer content */ public function testOverwriteWithLongerContent(): void { $this->out->expects($this->exactly(5)) ->method('write') ->with( ...self::withConsecutive( ['1'], // Backspaces [str_repeat("\x08", 1), 0], ['123', 0], // Backspaces [str_repeat("\x08", 3), 0], ['12345', 0] ) ) ->willReturn( 1, 1, 3, 3, 5 ); $this->io->out('1'); $this->io->overwrite('123', 0); $this->io->overwrite('12345', 0); } /** * Tests that setLoggers works properly */ public function testSetLoggers(): void { Log::drop('stdout'); Log::drop('stderr'); $this->io->setLoggers(true); $this->assertNotEmpty(Log::engine('stdout')); $this->assertNotEmpty(Log::engine('stderr')); $this->io->setLoggers(false); $this->assertNull(Log::engine('stdout')); $this->assertNull(Log::engine('stderr')); } /** * Tests that setLoggers does not add loggers if the * application already has a console logger. This * lets developers opt-out of the default behavior * by configuring something equivalent. */ public function testSetLoggersWithCustom(): void { Log::drop('stdout'); Log::drop('stderr'); Log::setConfig('console-logger', [ 'className' => 'Console', 'stream' => $this->out, 'types' => ['error', 'warning'], ]); $this->io->setLoggers(true); $this->assertEmpty(Log::engine('stdout')); $this->assertEmpty(Log::engine('stderr')); $this->assertNotEmpty(Log::engine('console-logger')); $this->io->setLoggers(false); $this->assertNull(Log::engine('stdout')); $this->assertNull(Log::engine('stderr')); $this->assertNotEmpty(Log::engine('console-logger')); } /** * Tests that setLoggers works properly with quiet */ public function testSetLoggersQuiet(): void { Log::drop('stdout'); Log::drop('stderr'); $this->io->setLoggers(ConsoleIo::QUIET); $this->assertEmpty(Log::engine('stdout')); $this->assertNotEmpty(Log::engine('stderr')); } /** * Tests that setLoggers works properly with verbose */ public function testSetLoggersVerbose(): void { Log::drop('stdout'); Log::drop('stderr'); $this->io->setLoggers(ConsoleIo::VERBOSE); $this->assertNotEmpty(Log::engine('stderr')); /** @var \Cake\Log\Log $engine */ $engine = Log::engine('stdout'); $this->assertEquals(['notice', 'info', 'debug'], $engine->getConfig('levels')); } /** * Ensure that setStyle() just proxies to stdout. */ public function testSetStyle(): void { $this->out->expects($this->once()) ->method('setStyle') ->with('name', ['props']); $this->io->setStyle('name', ['props']); } /** * Ensure that getStyle() just proxies to stdout. */ public function testGetStyle(): void { $this->out->expects($this->once()) ->method('getStyle') ->with('name'); $this->io->getStyle('name'); } /** * Ensure that styles() just proxies to stdout. */ public function testStyles(): void { $this->out->expects($this->once()) ->method('styles'); $this->io->styles(); } /** * Test the helper method. */ public function testHelper(): void { $this->out->expects($this->once()) ->method('write') ->with('It works!well ish'); $helper = $this->io->helper('simple'); $this->assertInstanceOf(Helper::class, $helper); $helper->output(['well', 'ish']); } /** * Provider for output helpers * * @return array */ public static function outHelperProvider(): array { return [['info'], ['success'], ['comment']]; } /** * Provider for err helpers * * @return array */ public static function errHelperProvider(): array { return [['warning'], ['error']]; } /** * test out helper methods */ #[DataProvider('outHelperProvider')] public function testOutHelpers(string $method): void { $this->out->expects($this->exactly(2)) ->method('write') ->with( ...self::withConsecutive( [ "<{$method}>Just a test", 1], [["<{$method}>Just", "<{$method}>a test"], 1] ) ); $this->io->{$method}('Just a test'); $this->io->{$method}(['Just', 'a test']); } /** * test err helper methods */ #[DataProvider('errHelperProvider')] public function testErrHelpers(string $method): void { $this->err->expects($this->exactly(2)) ->method('write') ->with( ...self::withConsecutive( [ "<{$method}>Just a test", 1], [["<{$method}>Just", "<{$method}>a test"], 1] ) ); $this->io->{$method}('Just a test'); $this->io->{$method}(['Just', 'a test']); } /** * Test that createFile */ public function testCreateFileSuccess(): void { $this->err->expects($this->never()) ->method('write'); $path = TMP . 'shell_test'; mkdir($path); $file = $path . DS . 'file1.php'; $contents = 'some content'; $result = $this->io->createFile($file, $contents); $this->assertTrue($result); $this->assertFileExists($file); $this->assertStringEqualsFile($file, $contents); } public function testCreateFileEmptySuccess(): void { $this->err->expects($this->never()) ->method('write'); $path = TMP . 'shell_test'; mkdir($path); $file = $path . DS . 'file_empty.php'; $contents = ''; $result = $this->io->createFile($file, $contents); $this->assertTrue($result); $this->assertFileExists($file); $this->assertStringEqualsFile($file, $contents); } public function testCreateFileDirectoryCreation(): void { $this->err->expects($this->never()) ->method('write'); $directory = TMP . 'shell_test'; $this->assertFileDoesNotExist($directory, 'Directory should not exist before createFile'); $path = $directory . DS . 'create.txt'; $contents = 'some content'; $result = $this->io->createFile($path, $contents); $this->assertTrue($result, 'File should create'); $this->assertFileExists($path); $this->assertStringEqualsFile($path, $contents); } /** * Test that createFile with permissions error. */ public function testCreateFilePermissionsError(): void { $this->skipIf(DS === '\\', 'Cant perform operations using permissions on windows.'); $path = TMP . 'shell_test'; $file = $path . DS . 'no_perms'; if (!is_dir($path)) { mkdir($path); } chmod($path, 0444); $this->io->createFile($file, 'testing'); $this->assertFileDoesNotExist($file); chmod($path, 0744); rmdir($path); } /** * Test that `q` raises an error. */ public function testCreateFileOverwriteQuit(): void { $path = TMP . 'shell_test'; mkdir($path); $file = $path . DS . 'file1.php'; touch($file); $this->expectException(StopException::class); $this->in->expects($this->once()) ->method('read') ->willReturn('q'); $this->io->createFile($file, 'some content'); } /** * Test that `n` raises an error. */ public function testCreateFileOverwriteNo(): void { $path = TMP . 'shell_test'; mkdir($path); $file = $path . DS . 'file1.php'; file_put_contents($file, 'original'); touch($file); $this->in->expects($this->once()) ->method('read') ->willReturn('n'); $contents = 'new content'; $result = $this->io->createFile($file, $contents); $this->assertFalse($result); $this->assertFileExists($file); $this->assertStringEqualsFile($file, 'original'); } /** * Test the forceOverwrite parameter */ public function testCreateFileOverwriteParam(): void { $path = TMP . 'shell_test'; mkdir($path); $file = $path . DS . 'file1.php'; file_put_contents($file, 'original'); touch($file); $contents = 'new content'; $result = $this->io->createFile($file, $contents, true); $this->assertTrue($result); $this->assertFileExists($file); $this->assertStringEqualsFile($file, $contents); } /** * Test the `a` response */ public function testCreateFileOverwriteAll(): void { $path = TMP . 'shell_test'; mkdir($path); $file = $path . DS . 'file1.php'; file_put_contents($file, 'original'); touch($file); $this->in->expects($this->once()) ->method('read') ->willReturn('a'); $this->io->createFile($file, 'new content'); $this->assertStringEqualsFile($file, 'new content'); $this->io->createFile($file, 'newer content'); $this->assertStringEqualsFile($file, 'newer content'); $this->io->createFile($file, 'newest content', false); $this->assertStringEqualsFile( $file, 'newest content', 'overwrite state replaces parameter' ); } }