@@ -13,6 +13,7 @@ import {
1313 markPackagistDailyProcessed ,
1414 markPackagistMetadataScanned ,
1515} from '@crowd/data-access-layer/src/packages/packagistPackageState'
16+ import { upsertPackagistVersions } from '@crowd/data-access-layer/src/packages/versions'
1617import type { QueryExecutor } from '@crowd/data-access-layer/src/queryExecutor'
1718
1819// Executes the real DAL functions against a capturing stand-in executor — no database.
@@ -256,3 +257,35 @@ describe('getCriticalPackagistPackageCount', () => {
256257 expect ( sql ) . toMatch ( / i s _ c r i t i c a l / )
257258 } )
258259} )
260+
261+ // The stale-is_latest cleanup only ever flips rows OUTSIDE the upsert batch (batch rows
262+ // already had is_latest set by the upsert CTE), so its row count must feed changedFields
263+ // separately or those real changes never reach the audit log.
264+ describe ( 'upsertPackagistVersions is_latest cleanup audit' , ( ) => {
265+ const versions = [
266+ { number : '2.0.0' , publishedAt : null , isLatest : true , isPrerelease : false , licenses : null } ,
267+ ]
268+
269+ it ( "appends versions.is_latest when the cleanup cleared a stale row the CTE diff can't see" , async ( ) => {
270+ qx . selectOne . mockResolvedValue ( { changed_fields : [ ] , version_ids : [ ] } )
271+ qx . result . mockResolvedValue ( 1 )
272+
273+ const { changedFields } = await upsertPackagistVersions ( asQx ( qx ) , '7' , versions , '2.0.0' )
274+
275+ const [ cleanupSql ] = qx . result . mock . calls [ 0 ]
276+ expect ( cleanupSql ) . toMatch ( / i s _ l a t e s t = f a l s e / )
277+ expect ( changedFields ) . toContain ( 'versions.is_latest' )
278+ } )
279+
280+ it ( 'reports nothing extra when the cleanup cleared no rows, and never duplicates the CTE diff' , async ( ) => {
281+ qx . selectOne . mockResolvedValue ( { changed_fields : [ 'versions.is_latest' ] , version_ids : [ ] } )
282+ qx . result . mockResolvedValue ( 0 )
283+
284+ const noClear = await upsertPackagistVersions ( asQx ( qx ) , '7' , versions , '2.0.0' )
285+ expect ( noClear . changedFields ) . toEqual ( [ 'versions.is_latest' ] )
286+
287+ qx . result . mockResolvedValue ( 2 )
288+ const cleared = await upsertPackagistVersions ( asQx ( qx ) , '7' , versions , '2.0.0' )
289+ expect ( cleared . changedFields . filter ( ( f ) => f === 'versions.is_latest' ) ) . toHaveLength ( 1 )
290+ } )
291+ } )
0 commit comments