From bd8d142101afd244f4bff62d8013338a1a7d7a1e Mon Sep 17 00:00:00 2001 From: Dmitry Merkushin Date: Tue, 19 Dec 2023 00:00:44 -0600 Subject: [PATCH 1/6] Add clock interface and fix time-based issue in one test --- includes/class-sensei-utils.php | 2 +- includes/class-sensei.php | 23 ++++++++ includes/clock/class-clock-interface.php | 25 +++++++++ includes/clock/class-clock.php | 47 ++++++++++++++++ tests/bootstrap.php | 13 +++++ tests/framework/class-sensei-clock-stub.php | 22 ++++++++ .../framework/trait-sensei-clock-helpers.php | 55 +++++++++++++++++++ tests/unit-tests/test-class-sensei-utils.php | 13 ++++- 8 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 includes/clock/class-clock-interface.php create mode 100644 includes/clock/class-clock.php create mode 100644 tests/framework/class-sensei-clock-stub.php create mode 100644 tests/framework/trait-sensei-clock-helpers.php diff --git a/includes/class-sensei-utils.php b/includes/class-sensei-utils.php index a30d0419dd..2bfcee5f5f 100644 --- a/includes/class-sensei-utils.php +++ b/includes/class-sensei-utils.php @@ -2835,7 +2835,7 @@ public static function output_query_params_as_inputs( array $excluded = [], stri */ public static function format_last_activity_date( string $date ) { $timezone = new DateTimeZone( 'GMT' ); - $now = new DateTime( 'now', $timezone ); + $now = Sensei()->clock->now( $timezone ); $date = new DateTime( $date, $timezone ); $diff_in_days = $now->diff( $date )->days; diff --git a/includes/class-sensei.php b/includes/class-sensei.php index 8544bc5b82..3f34106f99 100644 --- a/includes/class-sensei.php +++ b/includes/class-sensei.php @@ -1,5 +1,7 @@ install_version = $alloptions['sensei-install-version'] ?? null; + // Init the clock. + /** + * Filter the clock. + * + * @hook sensei_clock_init + * + * @since $$next-version$$ + * + * @param {Clock_Interface} $clock The clock. + * @return {Clock_Interface} Filtered clock. + */ + $clock = apply_filters( 'sensei_clock_init', new Clock( wp_timezone() ) ); + $this->clock = $clock; + // Initialize the core Sensei functionality $this->init(); diff --git a/includes/clock/class-clock-interface.php b/includes/clock/class-clock-interface.php new file mode 100644 index 0000000000..60a8f91423 --- /dev/null +++ b/includes/clock/class-clock-interface.php @@ -0,0 +1,25 @@ +timezone = $timezone; + } + + /** + * Get the current time. + * + * @param DateTimeZone|null $timezone The timezone to use. + * @return \DateTimeImmutable + */ + public function now( DateTimeZone $timezone = null ) { + return new \DateTimeImmutable( 'now', $timezone ?? $this->timezone ); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index f809c17b08..02bc5d2971 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -43,6 +43,9 @@ public function __construct() { // Enable features. tests_add_filter( 'sensei_feature_flag_tables_based_progress', '__return_true' ); + // Init clock. + tests_add_filter( 'sensei_clock_init', [ $this, 'init_clock' ] ); + /* * Load PHPUnit Polyfills for the WP testing suite. * @see https://github.com/WordPress/wordpress-develop/pull/1563/ @@ -85,6 +88,15 @@ public function load_sensei() { add_filter( 'sensei_scheduler_class', [ __CLASS__, 'scheduler_use_shim' ] ); } + public function init_clock() { + // Testing setup for clock. + require_once SENSEI_TEST_FRAMEWORK_DIR . '/class-sensei-clock-stub.php'; + + // Set the clock to a fixed time. + $clock = new Sensei_Clock_Stub(); + return $clock; + } + /** * Scheduler: Use shim. * @@ -120,6 +132,7 @@ public function includes() { require_once SENSEI_TEST_FRAMEWORK_DIR . '/trait-sensei-scheduler-test-helpers.php'; require_once SENSEI_TEST_FRAMEWORK_DIR . '/trait-sensei-test-redirect-helpers.php'; require_once SENSEI_TEST_FRAMEWORK_DIR . '/trait-sensei-hpps-helpers.php'; + require_once SENSEI_TEST_FRAMEWORK_DIR . '/trait-sensei-clock-helpers.php'; require_once SENSEI_TEST_FRAMEWORK_DIR . '/class-sensei-background-job-stub.php'; require_once SENSEI_TEST_FRAMEWORK_DIR . '/factories/class-sensei-factory.php'; require_once SENSEI_TEST_FRAMEWORK_DIR . '/factories/class-wp-unittest-factory-for-post-sensei.php'; diff --git a/tests/framework/class-sensei-clock-stub.php b/tests/framework/class-sensei-clock-stub.php new file mode 100644 index 0000000000..afce056da0 --- /dev/null +++ b/tests/framework/class-sensei-clock-stub.php @@ -0,0 +1,22 @@ +createMock( Clock_Interface::class ); + $clock->method( 'now' ) + ->willReturnOnConsecutiveCalls( + ...$return_values + ); + + Sensei()->clock = $clock; + } + + /** + * Reset the clock to the default. + */ + private function reset_clock() { + Sensei()->clock = new Sensei_Clock_Stub(); + } +} diff --git a/tests/unit-tests/test-class-sensei-utils.php b/tests/unit-tests/test-class-sensei-utils.php index fec949ce7d..b52599c68f 100644 --- a/tests/unit-tests/test-class-sensei-utils.php +++ b/tests/unit-tests/test-class-sensei-utils.php @@ -16,6 +16,7 @@ */ class Sensei_Utils_Test extends WP_UnitTestCase { use \Sensei_File_System_Helper; + use \Sensei_Clock_Helpers; /** * Setup function. @@ -286,10 +287,14 @@ public function testOutputQueryParamsAsInputs_WhenAParamIsExcluded_ReturnsCorrec * * @dataProvider lastActivityDateTestingData */ - public function testFormatLastActivityDate_WhenCalled_ReturnsCorrectlyFormattedDates( $minutes_count, $expected_output ) { + public function testFormatLastActivityDate_WhenCalled_ReturnsCorrectlyFormattedDates( $seconds_count, $expected_output ) { /* Arrange */ - $gmt_time = gmdate( 'Y-m-d H:i:s', strtotime( '-' . $minutes_count . ' seconds' ) ); - $date_as_per_format = wp_date( get_option( 'date_format' ), ( new DateTime( $gmt_time ) )->getTimestamp(), new DateTimeZone( 'GMT' ) ); + $current_datetime = new DateTimeImmutable( 'now', new DateTimeZone( 'GMT' ) ); + $this->set_clock_to( $current_datetime->getTimestamp() ); + + $test_time = $current_datetime->getTimestamp() - $seconds_count; + $gmt_time = gmdate( 'Y-m-d H:i:s', $test_time ); + $date_as_per_format = wp_date( get_option( 'date_format' ), $test_time, new DateTimeZone( 'GMT' ) ); /* Act */ $actual = Sensei_Utils::format_last_activity_date( $gmt_time ); @@ -297,6 +302,8 @@ public function testFormatLastActivityDate_WhenCalled_ReturnsCorrectlyFormattedD /* Assert */ $expected = empty( $expected_output ) ? $date_as_per_format : $expected_output; self::assertEquals( $expected, $actual, 'Last activity date is not being formatted correctly' ); + + $this->reset_clock(); } public function testSenseiGradeQuiz_WhenCalled_UpdatesTheFinalGrade() { From 14b935e20168791f86427877cc86a592eacdf4b0 Mon Sep 17 00:00:00 2001 From: Dmitry Merkushin Date: Mon, 25 Dec 2023 11:29:58 -0600 Subject: [PATCH 2/6] Add tests and changelog entry --- changelog/fix-time-based-tests | 4 +++ tests/unit-tests/clock/test-class-clock.php | 32 +++++++++++++++++++++ tests/unit-tests/test-class-sensei.php | 10 ++++++- 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 changelog/fix-time-based-tests create mode 100644 tests/unit-tests/clock/test-class-clock.php diff --git a/changelog/fix-time-based-tests b/changelog/fix-time-based-tests new file mode 100644 index 0000000000..85a8adb0c7 --- /dev/null +++ b/changelog/fix-time-based-tests @@ -0,0 +1,4 @@ +Significance: patch +Type: development + +Introduce Clock interface and corresponding public property for Sensei object. diff --git a/tests/unit-tests/clock/test-class-clock.php b/tests/unit-tests/clock/test-class-clock.php new file mode 100644 index 0000000000..d9db6a1a0e --- /dev/null +++ b/tests/unit-tests/clock/test-class-clock.php @@ -0,0 +1,32 @@ +now(); + + // Assert. + $this->assertInstanceOf( \DateTimeImmutable::class, $now ); + } + + public function testNow_DateTimeZoneGiven_ReturnsDateTimeInGivenTimeZone() { + // Arrange. + $clock = new Clock( new \DateTimeZone( 'America/New_York' ) ); + + // Act. + $now = $clock->now(); + + // Assert. + $this->assertEquals( 'America/New_York', $now->getTimezone()->getName() ); + } +} diff --git a/tests/unit-tests/test-class-sensei.php b/tests/unit-tests/test-class-sensei.php index 0609726185..25f3ef3631 100644 --- a/tests/unit-tests/test-class-sensei.php +++ b/tests/unit-tests/test-class-sensei.php @@ -1,6 +1,6 @@ assertInstanceOf( Migration_Job_Scheduler::class, $sensei->migration_scheduler ); } + public function testConstructor_Always_InitializesClockProperty() { + /* Arrange. */ + $sensei = Sensei(); + + /* Assert. */ + $this->assertInstanceOf( Clock_Interface::class, $sensei->clock ); + } + /** * Create courses and comments for the course. * From ca91f3471f95767e64e9fba97b4d04778315336c Mon Sep 17 00:00:00 2001 From: Dmitry Merkushin Date: Mon, 25 Dec 2023 11:45:09 -0600 Subject: [PATCH 3/6] Fix linter issues --- tests/framework/trait-sensei-clock-helpers.php | 1 - tests/unit-tests/clock/test-class-clock.php | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/framework/trait-sensei-clock-helpers.php b/tests/framework/trait-sensei-clock-helpers.php index 169c716db8..80ae1f0ebc 100644 --- a/tests/framework/trait-sensei-clock-helpers.php +++ b/tests/framework/trait-sensei-clock-helpers.php @@ -36,7 +36,6 @@ private function set_clock_to( $timestamp, $timezone = 'UTC' ) { $return_values[] = new \DateTimeImmutable( "@$ts", new \DateTimeZone( $timezone ) ); } - $clock = $this->createMock( Clock_Interface::class ); $clock->method( 'now' ) ->willReturnOnConsecutiveCalls( diff --git a/tests/unit-tests/clock/test-class-clock.php b/tests/unit-tests/clock/test-class-clock.php index d9db6a1a0e..fd1cb48ff7 100644 --- a/tests/unit-tests/clock/test-class-clock.php +++ b/tests/unit-tests/clock/test-class-clock.php @@ -5,6 +5,8 @@ use Sensei\Clock\Clock; /** + * Test the Clock class. + * * @covers Sensei\Clock\Clock */ class Clock_Test extends \WP_UnitTestCase { From a4199a129f1b64620b1418e327b8749bae859a4e Mon Sep 17 00:00:00 2001 From: Dmitry Merkushin Date: Mon, 25 Dec 2023 13:47:58 -0600 Subject: [PATCH 4/6] Set timezone in the mock --- tests/framework/class-sensei-clock-stub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/framework/class-sensei-clock-stub.php b/tests/framework/class-sensei-clock-stub.php index afce056da0..d7838ae885 100644 --- a/tests/framework/class-sensei-clock-stub.php +++ b/tests/framework/class-sensei-clock-stub.php @@ -17,6 +17,6 @@ class Sensei_Clock_Stub implements Clock_Interface { * @return \DateTimeImmutable */ public function now( \DateTimeZone $timezone = null ) { - return new \DateTimeImmutable( '@0', $timezone ?? new \DateTimeZone( 'UTC' ) ); + return ( new \DateTimeImmutable( '@0' ) )->setTimezone( $timezone ?? new \DateTimeZone( 'UTC' ) ); } } From 8bbfb0d0b67824431e866e8790f1de9681d5aa46 Mon Sep 17 00:00:00 2001 From: Dmitry Merkushin Date: Mon, 25 Dec 2023 14:49:40 -0600 Subject: [PATCH 5/6] Remove unnecessary assignment --- tests/bootstrap.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 02bc5d2971..ad03da42df 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -93,8 +93,7 @@ public function init_clock() { require_once SENSEI_TEST_FRAMEWORK_DIR . '/class-sensei-clock-stub.php'; // Set the clock to a fixed time. - $clock = new Sensei_Clock_Stub(); - return $clock; + return new Sensei_Clock_Stub(); } /** From 1d0f103b54c1c952da9748e49666d2e75fa7e056 Mon Sep 17 00:00:00 2001 From: Dmitry Merkushin Date: Fri, 29 Dec 2023 09:54:59 -0600 Subject: [PATCH 6/6] Remove unnecessary assignment --- includes/class-sensei.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/includes/class-sensei.php b/includes/class-sensei.php index 3f34106f99..f38ed2e61a 100644 --- a/includes/class-sensei.php +++ b/includes/class-sensei.php @@ -386,7 +386,6 @@ private function __construct( $main_plugin_file_name, $args ) { $alloptions = wp_load_alloptions(); $this->install_version = $alloptions['sensei-install-version'] ?? null; - // Init the clock. /** * Filter the clock. * @@ -397,8 +396,7 @@ private function __construct( $main_plugin_file_name, $args ) { * @param {Clock_Interface} $clock The clock. * @return {Clock_Interface} Filtered clock. */ - $clock = apply_filters( 'sensei_clock_init', new Clock( wp_timezone() ) ); - $this->clock = $clock; + $this->clock = apply_filters( 'sensei_clock_init', new Clock( wp_timezone() ) ); // Initialize the core Sensei functionality $this->init();