Commit c42209f0ec for woocommerce

commit c42209f0ecb146af67f971f6f71564f2d32a24a4
Author: Radoslav Georgiev <rageorgiev@gmail.com>
Date:   Mon Dec 22 15:34:50 2025 +0200

    Product Feed: Decrease expiration period, validate feed validity (#62537)

    * Decrease expiration to 20h, confirm that the field has not expired.

    * Add unit test

    * Add a positive test

diff --git a/plugins/woocommerce/src/Internal/ProductFeed/Integrations/POSCatalog/AsyncGenerator.php b/plugins/woocommerce/src/Internal/ProductFeed/Integrations/POSCatalog/AsyncGenerator.php
index 553342fa1e..0fb0f67e9e 100644
--- a/plugins/woocommerce/src/Internal/ProductFeed/Integrations/POSCatalog/AsyncGenerator.php
+++ b/plugins/woocommerce/src/Internal/ProductFeed/Integrations/POSCatalog/AsyncGenerator.php
@@ -44,7 +44,7 @@ class AsyncGenerator {
 	 *
 	 * @var int
 	 */
-	const FEED_EXPIRY = 24 * HOUR_IN_SECONDS;
+	const FEED_EXPIRY = 20 * HOUR_IN_SECONDS;

 	/**
 	 * Possible states of generation.
@@ -334,15 +334,26 @@ class AsyncGenerator {
 	 * @return bool         True if the status is valid, false otherwise.
 	 */
 	private function validate_status( array $status ): bool {
-		// Validate the state.
 		/**
 		 * For completed jobs, make sure the file still exists. Regenerate otherwise.
 		 *
 		 * The file should typically get deleted at the same time as the status is cleared.
 		 * However, something else could cause the file to disappear in the meantime (ex. manual delete).
+		 *
+		 * Also, if the cleanup job failed, the feed might appear as complete, but be expired.
 		 */
-		if ( self::STATE_COMPLETED === $status['state'] && ! file_exists( $status['path'] ) ) {
-			return false;
+		if ( self::STATE_COMPLETED === $status['state'] ) {
+			if ( ! file_exists( $status['path'] ) ) {
+				return false;
+			}
+
+			if ( ! isset( $status['completed_at'] ) ) {
+				return false;
+			}
+
+			if ( $status['completed_at'] + self::FEED_EXPIRY < time() ) {
+				return false;
+			}
 		}

 		/**
diff --git a/plugins/woocommerce/tests/php/src/Internal/ProductFeed/Integrations/POSCatalog/AsyncGeneratorTest.php b/plugins/woocommerce/tests/php/src/Internal/ProductFeed/Integrations/POSCatalog/AsyncGeneratorTest.php
index cd7fd0de06..728fc65850 100644
--- a/plugins/woocommerce/tests/php/src/Internal/ProductFeed/Integrations/POSCatalog/AsyncGeneratorTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/ProductFeed/Integrations/POSCatalog/AsyncGeneratorTest.php
@@ -7,6 +7,7 @@ use PHPUnit\Framework\MockObject\MockObject;
 use Automattic\WooCommerce\Internal\ProductFeed\Integrations\POSCatalog\AsyncGenerator;
 use Automattic\WooCommerce\Internal\ProductFeed\Integrations\POSCatalog\POSIntegration;
 use Automattic\WooCommerce\Internal\ProductFeed\Integrations\POSCatalog\ProductMapper;
+use ReflectionClass;
 use WC_Helper_Product;

 /**
@@ -103,4 +104,36 @@ class AsyncGeneratorTest extends \WC_Unit_Test_Case {
 		$updated_status = get_option( self::OPTION_KEY );
 		$this->assertEquals( AsyncGenerator::STATE_COMPLETED, $updated_status['state'] );
 	}
+
+	/**
+	 * Test that validate_status returns false for expired feeds.
+	 */
+	public function test_validate_status_returns_false_for_expired_feed() {
+		$status = array(
+			'state'        => AsyncGenerator::STATE_COMPLETED,
+			'path'         => __FILE__, // We just need a path that exists.
+			'completed_at' => time() - AsyncGenerator::FEED_EXPIRY - 1,
+		);
+
+		$method = ( new ReflectionClass( $this->sut ) )->getMethod( 'validate_status' );
+		$method->setAccessible( true );
+
+		$this->assertFalse( $method->invoke( $this->sut, $status ) );
+	}
+
+	/**
+	 * Test that validate_status returns true for non-expired feeds.
+	 */
+	public function test_validate_status_returns_true_for_non_expired_feed() {
+		$status = array(
+			'state'        => AsyncGenerator::STATE_COMPLETED,
+			'path'         => __FILE__, // We just need a path that exists.
+			'completed_at' => time() + AsyncGenerator::FEED_EXPIRY,
+		);
+
+		$method = ( new ReflectionClass( $this->sut ) )->getMethod( 'validate_status' );
+		$method->setAccessible( true );
+
+		$this->assertTrue( $method->invoke( $this->sut, $status ) );
+	}
 }