diff --git a/.github/workflows/phpunit-tests.yml b/.github/workflows/phpunit-tests.yml
index 9aabbb7..eef2b75 100644
--- a/.github/workflows/phpunit-tests.yml
+++ b/.github/workflows/phpunit-tests.yml
@@ -35,8 +35,16 @@ jobs:
php-version: ${{ matrix.php-version }}
tools: phpunit-polyfills:1.1
+ - name: Install SVN
+ run: sudo apt install -y subversion
+
- name: Setup tests
run: bash bin/install-wp-tests.sh wordpress_tests root root 127.0.0.1 latest true
+ - name: Prime the WordPress install for tests
+ id: prime-tests
+ run: XDEBUG_MODE=off phpunit${{ matrix.multisite && ' -c tests/phpunit/multisite.xml' || '' }} --filter gha_install_wp > /dev/null 2>&1
+
- name: Run tests
- run: XDEBUG_MODE=off phpunit${{ matrix.multisite && ' -c tests/phpunit/multisite.xml' || '' }}
+ if: steps.prime-tests.outcome == 'success'
+ run: XDEBUG_MODE=off WP_TESTS_SKIP_INSTALL=1 phpunit${{ matrix.multisite && ' -c tests/phpunit/multisite.xml' || '' }}
diff --git a/includes/class-admin-settings.php b/includes/class-admin-settings.php
index 414b604..b075cc3 100644
--- a/includes/class-admin-settings.php
+++ b/includes/class-admin-settings.php
@@ -506,6 +506,8 @@ public function register_settings() {
/**
* The Fields API which any CMS should have in its core but something we dont, hence this ugly hack.
*
+ * @codeCoverageIgnore Test with E2E tests instead.
+ *
* @param array $args The Field Parameters.
*
* @return void Echos the Field HTML.
diff --git a/includes/class-debug.php b/includes/class-debug.php
index eba51fd..81ab276 100644
--- a/includes/class-debug.php
+++ b/includes/class-debug.php
@@ -63,7 +63,7 @@ private static function verify_filesystem( $wp_filesystem ) {
/**
* Log error in file write fails only if debug is set to true. This is a valid use case.
*/
- error_log( 'AspireUpdate - Could not open or write to the file system. Check file system permissions to debug log directory.' );
+ error_log( 'AspireUpdate - Could not open or write to the file system. Check file system permissions to debug log directory.' ); // @codeCoverageIgnore
// phpcs:enable
}
return false;
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 081e4b7..66ac36d 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -61,9 +61,25 @@
- /tests/phpunit/tests/*
+ /tests/phpunit/*
+ /tests/phpunit/*
+
+
+
+
/tests/phpunit/tests/*
+
+
+
+ /tests/phpunit/*
+
+
+ /tests/phpunit/*
+
+
+ /tests/phpunit/*
+
diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php
index 3899953..186c80a 100644
--- a/tests/phpunit/bootstrap.php
+++ b/tests/phpunit/bootstrap.php
@@ -40,5 +40,9 @@ function _manually_load_plugin() {
// Start up the WP testing environment.
require "{$_tests_dir}/includes/bootstrap.php";
+// Load unit test mocks and fakes.
+require __DIR__ . '/includes/class-ap-fake-filesystem.php';
+
// Load unit test abstract classes.
require __DIR__ . '/includes/AdminSettings_UnitTestCase.php';
+require __DIR__ . '/includes/Debug_UnitTestCase.php';
diff --git a/tests/phpunit/includes/Debug_UnitTestCase.php b/tests/phpunit/includes/Debug_UnitTestCase.php
new file mode 100644
index 0000000..0058fd0
--- /dev/null
+++ b/tests/phpunit/includes/Debug_UnitTestCase.php
@@ -0,0 +1,116 @@
+setAccessible( true );
+ self::$log_file = $get_file_path->invoke( null );
+ $get_file_path->setAccessible( false );
+
+ if ( file_exists( self::$log_file ) ) {
+ unlink( self::$log_file );
+ }
+
+ if ( isset( $GLOBALS['wp_filesystem'] ) ) {
+ self::$default_filesystem = $GLOBALS['wp_filesystem'];
+ } else {
+ self::$default_filesystem = false;
+ }
+ }
+
+ /**
+ * Filters the filesystem method before each test runs.
+ *
+ * Filters are removed in the tear_down() parent method.
+ *
+ * @return void
+ */
+ public function set_up() {
+ parent::set_up();
+
+ add_filter(
+ 'filesystem_method',
+ static function () {
+ return 'direct';
+ }
+ );
+ }
+
+ /**
+ * Delete the log file and restores the filesystem after each test runs.
+ *
+ * @return void
+ */
+ public function tear_down() {
+ if ( file_exists( self::$log_file ) ) {
+ unlink( self::$log_file );
+ }
+
+ if ( false === self::$default_filesystem ) {
+ unset( $GLOBALS['wp_filesystem'] );
+ } else {
+ $GLOBALS['wp_filesystem'] = self::$default_filesystem;
+ }
+
+ parent::tear_down();
+ }
+
+ /**
+ * Creates a fake filesystem.
+ *
+ * @param bool|null $exists Whether paths should exist.
+ * Default null uses default implemenation.
+ * @param bool|null $is_readable Whether paths should be readable.
+ * Default null uses default implemenation.
+ * @param bool|null $is_writable Whether paths should be writable.
+ * Default null uses default implemenation.
+ */
+ public function get_fake_filesystem( $exists = null, $is_readable = null, $is_writable = null ) {
+ $hash = ( null === $exists ? '-1' : (int) $exists ) . ',' .
+ ( null === $is_readable ? '-1' : (int) $is_readable ) . ',' .
+ ( null === $is_writable ? '-1' : (int) $is_writable );
+
+ if ( ! isset( self::$filesystems[ $hash ] ) ) {
+ self::$filesystems[ $hash ] = new AP_FakeFilesystem( $exists, $is_readable, $is_writable );
+ }
+
+ return self::$filesystems[ $hash ];
+ }
+}
diff --git a/tests/phpunit/includes/class-ap-fake-filesystem.php b/tests/phpunit/includes/class-ap-fake-filesystem.php
new file mode 100644
index 0000000..d2de759
--- /dev/null
+++ b/tests/phpunit/includes/class-ap-fake-filesystem.php
@@ -0,0 +1,82 @@
+exists = $exists;
+ $this->is_readable = $is_readable;
+ $this->is_writable = $is_writable;
+ }
+
+ /**
+ * Checks whether a path exists.
+ *
+ * @param string $path The path to check.
+ * @return bool Whether the path exists.
+ */
+ public function exists( $path ) {
+ if ( null === $this->exists ) {
+ return parent::exists( $path );
+ }
+ return $this->exists;
+ }
+
+ /**
+ * Checks whether a path is readable.
+ *
+ * @param string $path The path to check.
+ * @return bool Whether the path is readable.
+ */
+ public function is_readable( $path ) {
+ if ( null === $this->is_readable ) {
+ return parent::is_readable( $path );
+ }
+ return $this->is_readable;
+ }
+
+ /**
+ * Checks whether a path is writable.
+ *
+ * @param string $path The path to check.
+ * @return bool Whether the path is writable.
+ */
+ public function is_writable( $path ) {
+ if ( null === $this->is_writable ) {
+ return parent::is_writable( $path );
+ }
+ return $this->is_writable;
+ }
+}
diff --git a/tests/phpunit/tests/Debug/Debug_ClearTest.php b/tests/phpunit/tests/Debug/Debug_ClearTest.php
new file mode 100644
index 0000000..d6882c9
--- /dev/null
+++ b/tests/phpunit/tests/Debug/Debug_ClearTest.php
@@ -0,0 +1,106 @@
+assertWPError(
+ AspireUpdate\Debug::clear(),
+ 'A WP_Error object was not returned.'
+ );
+
+ $this->assertFileDoesNotExist(
+ self::$log_file,
+ 'The log file was created.'
+ );
+ }
+
+ /**
+ * Test that a WP_Error object is returned when the log file doesn't exist.
+ *
+ * @covers \AspireUpdate\Debug::init_filesystem
+ * @covers \AspireUpdate\Debug::verify_filesystem
+ * @covers \AspireUpdate\Debug::get_file_path
+ */
+ public function test_should_return_wp_error_when_log_file_does_not_exist() {
+ $this->assertWPError(
+ AspireUpdate\Debug::clear(),
+ 'A WP_Error object was not returned.'
+ );
+
+ $this->assertFileDoesNotExist(
+ self::$log_file,
+ 'The log file was created.'
+ );
+ }
+
+ /**
+ * Test that a WP_Error object is returned when the log file isn't writable.
+ *
+ * @covers \AspireUpdate\Debug::init_filesystem
+ * @covers \AspireUpdate\Debug::verify_filesystem
+ * @covers \AspireUpdate\Debug::get_file_path
+ */
+ public function test_should_return_wp_error_when_log_file_is_not_writable() {
+ global $wp_filesystem;
+
+ // Backup and replace the filesystem object.
+ $wp_filesystem = $this->get_fake_filesystem( true, true, false );
+
+ $actual = AspireUpdate\Debug::clear();
+
+ $this->assertWPError(
+ $actual,
+ 'A WP_Error was not returned.'
+ );
+
+ $this->assertFileDoesNotExist(
+ self::$log_file,
+ 'The log file was created.'
+ );
+ }
+
+ /**
+ * Test that the log file is cleared.
+ */
+ public function test_should_clear_log_file() {
+ file_put_contents(
+ self::$log_file,
+ "First line\r\nSecond line\r\nThird line"
+ );
+
+ $this->assertFileExists(
+ self::$log_file,
+ 'The log file was not created before testing.'
+ );
+
+ AspireUpdate\Debug::clear();
+
+ $this->assertFileExists(
+ self::$log_file,
+ 'The log file was deleted.'
+ );
+
+ $this->assertSame(
+ '',
+ file_get_contents( self::$log_file ),
+ 'The log file was not cleared.'
+ );
+ }
+}
diff --git a/tests/phpunit/tests/Debug/Debug_LogRequestTest.php b/tests/phpunit/tests/Debug/Debug_LogRequestTest.php
new file mode 100644
index 0000000..e16ba57
--- /dev/null
+++ b/tests/phpunit/tests/Debug/Debug_LogRequestTest.php
@@ -0,0 +1,104 @@
+assertFileDoesNotExist( self::$log_file );
+ }
+
+ /**
+ * Test that nothing is written to the log file when debug types are not an array.
+ */
+ public function test_should_not_write_to_log_file_when_debug_types_are_not_an_array() {
+ define( 'AP_DEBUG', true );
+ define( 'AP_DEBUG_TYPES', 'request' );
+
+ AspireUpdate\Debug::log_request( 'Test log message.' );
+
+ $this->assertFileDoesNotExist( self::$log_file );
+ }
+
+ /**
+ * Test that nothing is written to the log file when request debugging is disabled.
+ */
+ public function test_should_not_write_to_log_file_when_request_debugging_is_disabled() {
+ define( 'AP_DEBUG', true );
+ define( 'AP_DEBUG_TYPES', [ 'response', 'string' ] );
+
+ AspireUpdate\Debug::log_request( 'Test log message.' );
+
+ $this->assertFileDoesNotExist( self::$log_file );
+ }
+
+ /**
+ * Test that the message is written to the log file.
+ *
+ * @dataProvider data_debug_types
+ *
+ * @param array $debug_types An array of enabled debug types.
+ */
+ public function test_should_write_to_log_file( $debug_types ) {
+ define( 'AP_DEBUG', true );
+ define( 'AP_DEBUG_TYPES', $debug_types );
+
+ $message = 'Test log message.';
+
+ AspireUpdate\Debug::log_request( $message );
+
+ $this->assertFileExists(
+ self::$log_file,
+ 'The log file was created.'
+ );
+
+ $this->assertStringContainsString(
+ $message,
+ file_get_contents( self::$log_file ),
+ 'The message was not logged.'
+ );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_debug_types() {
+ return [
+ 'just "request"' => [
+ 'debug_types' => [ 'request' ],
+ ],
+ '"request" at the start of the array"' => [
+ 'debug_types' => [ 'request', 'response' ],
+ ],
+ '"request" in the middle of the array"' => [
+ 'debug_types' => [ 'string', 'request', 'response' ],
+ ],
+ '"request" at the end of the array"' => [
+ 'debug_types' => [ 'string', 'response', 'request' ],
+ ],
+ ];
+ }
+}
diff --git a/tests/phpunit/tests/Debug/Debug_LogResponseTest.php b/tests/phpunit/tests/Debug/Debug_LogResponseTest.php
new file mode 100644
index 0000000..8be3605
--- /dev/null
+++ b/tests/phpunit/tests/Debug/Debug_LogResponseTest.php
@@ -0,0 +1,104 @@
+assertFileDoesNotExist( self::$log_file );
+ }
+
+ /**
+ * Test that nothing is written to the log file when debug types are not an array.
+ */
+ public function test_should_not_write_to_log_file_when_debug_types_are_not_an_array() {
+ define( 'AP_DEBUG', true );
+ define( 'AP_DEBUG_TYPES', 'response' );
+
+ AspireUpdate\Debug::log_response( 'Test log message.' );
+
+ $this->assertFileDoesNotExist( self::$log_file );
+ }
+
+ /**
+ * Test that nothing is written to the log file when response debugging is disabled.
+ */
+ public function test_should_not_write_to_log_file_when_response_debugging_is_disabled() {
+ define( 'AP_DEBUG', true );
+ define( 'AP_DEBUG_TYPES', [ 'request', 'string' ] );
+
+ AspireUpdate\Debug::log_response( 'Test log message.' );
+
+ $this->assertFileDoesNotExist( self::$log_file );
+ }
+
+ /**
+ * Test that the message is written to the log file.
+ *
+ * @dataProvider data_debug_types
+ *
+ * @param array $debug_types An array of enabled debug types.
+ */
+ public function test_should_write_to_log_file( $debug_types ) {
+ define( 'AP_DEBUG', true );
+ define( 'AP_DEBUG_TYPES', $debug_types );
+
+ $message = 'Test log message.';
+
+ AspireUpdate\Debug::log_response( $message );
+
+ $this->assertFileExists(
+ self::$log_file,
+ 'The log file was created.'
+ );
+
+ $this->assertStringContainsString(
+ $message,
+ file_get_contents( self::$log_file ),
+ 'The message was not logged.'
+ );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_debug_types() {
+ return [
+ 'just "response"' => [
+ 'debug_types' => [ 'response' ],
+ ],
+ '"response" at the start of the array"' => [
+ 'debug_types' => [ 'response', 'request' ],
+ ],
+ '"response" in the middle of the array"' => [
+ 'debug_types' => [ 'string', 'response', 'request' ],
+ ],
+ '"response" at the end of the array"' => [
+ 'debug_types' => [ 'string', 'request', 'response' ],
+ ],
+ ];
+ }
+}
diff --git a/tests/phpunit/tests/Debug/Debug_LogStringTest.php b/tests/phpunit/tests/Debug/Debug_LogStringTest.php
new file mode 100644
index 0000000..14508a6
--- /dev/null
+++ b/tests/phpunit/tests/Debug/Debug_LogStringTest.php
@@ -0,0 +1,104 @@
+assertFileDoesNotExist( self::$log_file );
+ }
+
+ /**
+ * Test that nothing is written to the log file when debug types are not an array.
+ */
+ public function test_should_not_write_to_log_file_when_debug_types_are_not_an_array() {
+ define( 'AP_DEBUG', true );
+ define( 'AP_DEBUG_TYPES', 'string' );
+
+ AspireUpdate\Debug::log_string( 'Test log message.' );
+
+ $this->assertFileDoesNotExist( self::$log_file );
+ }
+
+ /**
+ * Test that nothing is written to the log file when string debugging is disabled.
+ */
+ public function test_should_not_write_to_log_file_when_string_debugging_is_disabled() {
+ define( 'AP_DEBUG', true );
+ define( 'AP_DEBUG_TYPES', [ 'request', 'response' ] );
+
+ AspireUpdate\Debug::log_string( 'Test log message.' );
+
+ $this->assertFileDoesNotExist( self::$log_file );
+ }
+
+ /**
+ * Test that the message is written to the log file.
+ *
+ * @dataProvider data_debug_types
+ *
+ * @param array $debug_types An array of enabled debug types.
+ */
+ public function test_should_write_to_log_file( $debug_types ) {
+ define( 'AP_DEBUG', true );
+ define( 'AP_DEBUG_TYPES', $debug_types );
+
+ $message = 'Test log message.';
+
+ AspireUpdate\Debug::log_string( $message );
+
+ $this->assertFileExists(
+ self::$log_file,
+ 'The log file was created.'
+ );
+
+ $this->assertStringContainsString(
+ $message,
+ file_get_contents( self::$log_file ),
+ 'The message was not logged.'
+ );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_debug_types() {
+ return [
+ 'just "string"' => [
+ 'debug_types' => [ 'string' ],
+ ],
+ '"string" at the start of the array"' => [
+ 'debug_types' => [ 'string', 'request' ],
+ ],
+ '"string" in the middle of the array"' => [
+ 'debug_types' => [ 'request', 'string', 'response' ],
+ ],
+ '"string" at the end of the array"' => [
+ 'debug_types' => [ 'request', 'response', 'string' ],
+ ],
+ ];
+ }
+}
diff --git a/tests/phpunit/tests/Debug/Debug_LogTest.php b/tests/phpunit/tests/Debug/Debug_LogTest.php
new file mode 100644
index 0000000..c95036f
--- /dev/null
+++ b/tests/phpunit/tests/Debug/Debug_LogTest.php
@@ -0,0 +1,210 @@
+assertFileDoesNotExist(
+ self::$log_file,
+ 'The log file was created.'
+ );
+ }
+
+ /**
+ * Test that the log file is created when it doesn't already exist.
+ */
+ public function test_should_create_log_file_if_it_does_not_already_exist() {
+ $this->assertFileDoesNotExist(
+ self::$log_file,
+ 'The log file already exists before testing.'
+ );
+
+ $message = 'Test log message.';
+
+ AspireUpdate\Debug::log( $message );
+
+ $this->assertFileExists(
+ self::$log_file,
+ 'The log file was not created.'
+ );
+ }
+
+ /**
+ * Test that the message is added to the log file.
+ *
+ * @covers \AspireUpdate\Debug::format_message
+ */
+ public function test_should_add_message_to_log_file() {
+ $this->assertFileDoesNotExist(
+ self::$log_file,
+ 'The log file already exists before testing.'
+ );
+
+ $message = 'Test log message.';
+
+ AspireUpdate\Debug::log( $message );
+
+ $this->assertFileExists(
+ self::$log_file,
+ 'The log file was not created.'
+ );
+
+ $this->assertStringContainsString(
+ $message,
+ file_get_contents( self::$log_file ),
+ 'The message was not added.'
+ );
+ }
+
+ /**
+ * Test that the message is prepended to an existing log file.
+ *
+ * @covers \AspireUpdate\Debug::format_message
+ */
+ public function test_should_add_message_to_an_existing_log_file() {
+ $existing_content = 'An existing log file.';
+ file_put_contents( self::$log_file, $existing_content );
+
+ $message = 'Test log message.';
+
+ AspireUpdate\Debug::log( $message );
+
+ $this->assertFileExists(
+ self::$log_file,
+ 'The log file was not created.'
+ );
+
+ $this->assertStringContainsString(
+ "$message\n$existing_content",
+ file_get_contents( self::$log_file ),
+ 'The message was not prepended to the log file.'
+ );
+ }
+
+ /**
+ * Test that the message is prefixed with the timestamp.
+ *
+ * @covers \AspireUpdate\Debug::format_message
+ */
+ public function test_should_prefix_message_with_timestamp() {
+ AspireUpdate\Debug::log( 'Test log message.' );
+
+ $this->assertFileExists(
+ self::$log_file,
+ 'The log file was not created.'
+ );
+
+ $this->assertMatchesRegularExpression(
+ '/^\[[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\]/',
+ file_get_contents( self::$log_file ),
+ 'The message was not prefixed with the timestamp.'
+ );
+ }
+
+ /**
+ * Test that the message is prefixed with its type.
+ *
+ * @dataProvider data_message_types
+ *
+ * @covers \AspireUpdate\Debug::format_message
+ *
+ * @param string $type The type of message.
+ */
+ public function test_should_prefix_message_with_type( $type ) {
+ $message = 'Test log message.';
+
+ AspireUpdate\Debug::log( $message, $type );
+
+ $this->assertFileExists(
+ self::$log_file,
+ 'The log file was not created.'
+ );
+
+ $this->assertStringContainsString(
+ '[' . strtoupper( $type ) . ']: ' . $message,
+ file_get_contents( self::$log_file ),
+ 'The message was not prefixed with its type.'
+ );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_message_types() {
+ return $this->text_array_to_dataprovider(
+ [
+ 'string',
+ 'request',
+ 'response',
+ 'custom',
+ ]
+ );
+ }
+
+ /**
+ * Test that array and object messages are expanded.
+ *
+ * @dataProvider data_arrays_and_objects
+ *
+ * @covers \AspireUpdate\Debug::format_message
+ *
+ * @param array|object $message The message.
+ */
+ public function test_should_expand_array_or_object_messages( $message ) {
+ AspireUpdate\Debug::log( $message );
+
+ $this->assertFileExists(
+ self::$log_file,
+ 'The log file was not created.'
+ );
+
+ $this->assertStringContainsString(
+ // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
+ print_r( $message, true ),
+ file_get_contents( self::$log_file ),
+ 'The array message was not expanded.'
+ );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_arrays_and_objects() {
+ return [
+ 'an array' => [
+ 'message' => [],
+ ],
+ 'a non-empty array' => [
+ 'message' => [ 'First line', 'Second line', 'Third line' ],
+ ],
+ 'an object with no properties' => [
+ 'message' => (object) [],
+ ],
+ 'an object with properties' => [
+ 'message' => (object) [ 'First line', 'Second line', 'Third line' ],
+ ],
+ ];
+ }
+}
diff --git a/tests/phpunit/tests/Debug/Debug_ReadTest.php b/tests/phpunit/tests/Debug/Debug_ReadTest.php
new file mode 100644
index 0000000..5f275e1
--- /dev/null
+++ b/tests/phpunit/tests/Debug/Debug_ReadTest.php
@@ -0,0 +1,187 @@
+assertWPError( AspireUpdate\Debug::read() );
+ }
+
+ /**
+ * Test that a WP_Error object is returned when the log file doesn't exist.
+ *
+ * @covers \AspireUpdate\Debug::init_filesystem
+ * @covers \AspireUpdate\Debug::verify_filesystem
+ * @covers \AspireUpdate\Debug::get_file_path
+ */
+ public function test_should_return_wp_error_when_log_file_does_not_exist() {
+ $this->assertWPError( AspireUpdate\Debug::read() );
+ }
+
+ /**
+ * Test that a WP_Error object is returned when the log file isn't readable.
+ *
+ * @covers \AspireUpdate\Debug::init_filesystem
+ * @covers \AspireUpdate\Debug::verify_filesystem
+ * @covers \AspireUpdate\Debug::get_file_path
+ */
+ public function test_should_return_wp_error_when_log_file_is_not_readable() {
+ global $wp_filesystem;
+
+ // Create the log file.
+ file_put_contents( self::$log_file, '' );
+
+ // Backup and replace the filesystem object.
+ $wp_filesystem = $this->get_fake_filesystem( true, false, true );
+
+ $actual = AspireUpdate\Debug::read();
+
+ $this->assertWPError( $actual );
+ }
+
+ /**
+ * Test that an empty log message is returned when the log file is empty.
+ */
+ public function test_should_return_an_empty_log_message_when_log_file_is_empty() {
+ file_put_contents( self::$log_file, '' );
+
+ $actual = AspireUpdate\Debug::read();
+ $this->assertIsString(
+ $actual,
+ 'A string was not returned.'
+ );
+
+ $this->assertStringContainsString(
+ 'Log file is empty',
+ $actual,
+ 'The empty log file message was not returned.'
+ );
+ }
+
+ /**
+ * Test that an empty log message is returned when the log file's content
+ * is only empty space.
+ */
+ public function test_should_return_an_empty_log_message_when_log_file_only_has_empty_space() {
+ file_put_contents( self::$log_file, " \n\r\t\v\x00" );
+
+ $actual = AspireUpdate\Debug::read();
+ $this->assertIsString(
+ $actual,
+ 'A string was not returned.'
+ );
+
+ $this->assertStringContainsString(
+ 'Log file is empty',
+ $actual,
+ 'The empty log file message was not returned.'
+ );
+ }
+
+ /**
+ * Test that an empty log message is not returned when the log file
+ * has contents.
+ */
+ public function test_should_not_return_an_empty_log_message_when_log_file_has_contents() {
+ file_put_contents( self::$log_file, 'Some contents' );
+
+ $actual = AspireUpdate\Debug::read();
+ $this->assertIsString(
+ $actual,
+ 'A string was not returned.'
+ );
+
+ $this->assertStringNotContainsString(
+ 'Log file is empty',
+ $actual,
+ 'The empty log file message was returned.'
+ );
+ }
+
+ /**
+ * Test that a truncation message is added when the log file has more
+ * lines than requested.
+ */
+ public function test_should_add_a_truncation_message_when_log_file_has_more_lines_than_requested() {
+ file_put_contents(
+ self::$log_file,
+ "First line\r\nSecond line\r\nThird line"
+ );
+
+ $actual = AspireUpdate\Debug::read( 2 );
+
+ $this->assertIsString(
+ $actual,
+ 'A string was not returned.'
+ );
+
+ $this->assertStringContainsString(
+ 'Log truncated',
+ $actual,
+ 'The truncation message was not returned.'
+ );
+ }
+
+ /**
+ * Test that no truncation message is added when the log file has the same
+ * number of lines as requested.
+ */
+ public function test_should_not_add_a_truncation_message_when_log_file_has_the_same_number_of_lines_as_requested() {
+ file_put_contents(
+ self::$log_file,
+ "First line\r\nSecond line\r\nThird line"
+ );
+
+ $actual = AspireUpdate\Debug::read( 3 );
+
+ $this->assertIsString(
+ $actual,
+ 'A string was not returned.'
+ );
+
+ $this->assertStringNotContainsString(
+ 'Log truncated',
+ $actual,
+ 'The truncation message was added.'
+ );
+ }
+
+ /**
+ * Test that no truncation message is added when the log file has fewer than
+ * lines than requested.
+ */
+ public function test_should_not_add_a_truncation_message_when_log_file_has_fewer_lines_than_requested() {
+ file_put_contents(
+ self::$log_file,
+ "First line\r\nSecond line\r\nThird line"
+ );
+
+ $actual = AspireUpdate\Debug::read( 4 );
+
+ $this->assertIsString(
+ $actual,
+ 'A string was not returned.'
+ );
+
+ $this->assertStringNotContainsString(
+ 'Log truncated',
+ $actual,
+ 'The truncation message was added.'
+ );
+ }
+}