Commit b7d54e003b9 for woocommerce

commit b7d54e003b93771b5f832bfd7e7745267e519012
Author: AbdulRaheem Alick <99078532+abdulraheemalick@users.noreply.github.com>
Date:   Wed Nov 4 00:17:03 2026 +0800

    Fix the collections store ignoring the Store API Last-Modified date (#70363)

    * Fix the collections store ignoring the Store API Last-Modified date

    Since 10.7.0 the Store API products route sends Last-Modified as an
    HTTP-date, which parseInt reads as NaN, so the collections store never
    invalidated its cached collections. Read the header as a date, still
    accept a Unix timestamp, and ignore a value that cannot be read.

    * Read the Last-Modified header only as an HTTP-date

    Nothing in WooCommerce has sent Last-Modified as a Unix timestamp since
    10.7.0, so remove the integer branch and its test, and inline the parse
    in getCollection(), as the review asked. A value that Date.parse()
    cannot read gives NaN and is still skipped. The collections tests pass
    (25 of 25), and ESLint reports nothing on the two files.

    * Use a generic changelog entry and a stricter test check

    The review found that an outdated product list is very hard to
    reproduce in the UI, so the changelog entry now names the store fix
    instead of a visible effect. The test for an unreadable header now
    compares the whole action, as the review suggested. The collections
    tests pass (25 of 25), the changed test still fails against trunk's
    resolvers.js, and ESLint reports nothing on the test file.

diff --git a/plugins/woocommerce/changelog/fix-store-api-collections-last-modified-header b/plugins/woocommerce/changelog/fix-store-api-collections-last-modified-header
new file mode 100644
index 00000000000..044e2bb6c65
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-store-api-collections-last-modified-header
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix the collections data store ignoring the Store API Last-Modified header.
diff --git a/plugins/woocommerce/client/blocks/packages/public-api/block-data/collections/resolvers.js b/plugins/woocommerce/client/blocks/packages/public-api/block-data/collections/resolvers.js
index df7b96c4876..3a4eb7e2860 100644
--- a/plugins/woocommerce/client/blocks/packages/public-api/block-data/collections/resolvers.js
+++ b/plugins/woocommerce/client/blocks/packages/public-api/block-data/collections/resolvers.js
@@ -58,11 +58,16 @@ export function* getCollection( namespace, resourceName, query, ids ) {
 			yield apiFetchWithHeadersControl( { path: route + queryString } );

 		if ( headers && headers.get && headers.has( 'last-modified' ) ) {
+			// The header is an HTTP-date. A value that cannot be parsed gives
+			// NaN and is ignored.
+			const lastModified = Math.floor(
+				Date.parse( headers.get( 'last-modified' ) ) / 1000
+			);
 			// Do any invalidation before the collection is received to prevent
 			// this query running again.
-			yield invalidateModifiedCollection(
-				parseInt( headers.get( 'last-modified' ), 10 )
-			);
+			if ( lastModified ) {
+				yield invalidateModifiedCollection( lastModified );
+			}
 		}

 		yield receiveCollection( namespace, resourceName, queryString, ids, {
diff --git a/plugins/woocommerce/client/blocks/packages/public-api/block-data/collections/test/resolvers.js b/plugins/woocommerce/client/blocks/packages/public-api/block-data/collections/test/resolvers.js
index b481e659805..585a83ef97f 100644
--- a/plugins/woocommerce/client/blocks/packages/public-api/block-data/collections/test/resolvers.js
+++ b/plugins/woocommerce/client/blocks/packages/public-api/block-data/collections/test/resolvers.js
@@ -125,6 +125,78 @@ describe( 'getCollection', () => {
 	} );
 } );

+describe( 'getCollection with a Last-Modified header', () => {
+	const testArgs = [ 'wc/store/v1', 'products', { per_page: 1 }, [] ];
+	const httpDate = 'Tue, 20 Oct 2026 02:19:18 GMT';
+	const timestamp = 1792462758;
+
+	const fetchWithHeader = ( lastModified ) => {
+		const headers = {
+			get: ( name ) =>
+				name === 'last-modified' ? lastModified : undefined,
+			has: ( name ) => name === 'last-modified',
+		};
+		const fulfillment = getCollection( ...testArgs );
+		fulfillment.next();
+		fulfillment.next( 'https://example.org/wc/store/v1/products' );
+		const { value } = fulfillment.next( { response: [], headers } );
+		return { value, headers };
+	};
+
+	const runInvalidation = ( invalidation, storedLastModified ) => {
+		invalidation.next();
+		invalidation.next( storedLastModified );
+		invalidation.next();
+		invalidation.next();
+	};
+
+	beforeEach( () => {
+		jest.clearAllMocks();
+	} );
+
+	it( 'stores the time of an HTTP-date header as a Unix timestamp', () => {
+		const { value } = fetchWithHeader( httpDate );
+		runInvalidation( value, 0 );
+		expect( controls.dispatch ).toHaveBeenCalledWith(
+			STORE_KEY,
+			'receiveLastModified',
+			timestamp
+		);
+	} );
+
+	it( 'invalidates the store when an HTTP-date header is later than the stored time', () => {
+		const { value } = fetchWithHeader( httpDate );
+		runInvalidation( value, timestamp - 60 );
+		expect( controls.dispatch ).toHaveBeenCalledWith(
+			STORE_KEY,
+			'invalidateResolutionForStore'
+		);
+		expect( controls.dispatch ).toHaveBeenCalledWith(
+			STORE_KEY,
+			'receiveLastModified',
+			timestamp
+		);
+	} );
+
+	it( 'does not invalidate the store when the header matches the stored time', () => {
+		const { value } = fetchWithHeader( httpDate );
+		runInvalidation( value, timestamp );
+		expect( controls.dispatch ).not.toHaveBeenCalled();
+	} );
+
+	it( 'ignores a header it cannot read', () => {
+		const { value, headers } = fetchWithHeader( 'not a date' );
+		const expected = receiveCollection(
+			'wc/store/v1',
+			'products',
+			'?per_page=1',
+			[],
+			{ items: [], headers }
+		);
+		expect( value ).toEqual( expected );
+	} );
+} );
+
 describe( 'getCollectionHeader', () => {
 	let fulfillment;
 	const rewind = ( ...testArgs ) =>