Files
renovate/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts
Sebastian Poxhofer 58771bb183 refactor(linters): enabled oxlints func-style (#44691)
* refactor(oxlint): enable func-style

Enable the built-in func-style rule as ["warn", "declaration"] so that
named functions are written as function declarations instead of arrow or
function expressions assigned to variables, matching the convention in
docs/development/best-practices.md (Functions section). Inline arrow
callbacks are unaffected; prefer-arrow-callback continues to cover the
callback direction.

Severity is "warn" instead of "error" as a fallback: a repo-wide run
found 196 pre-existing violations (70 in spec files, 126 in production
code), exceeding the 80-violation fix threshold for this change, so no
violations were fixed here. Follow-up work can convert the offenders
(largest clusters: lib/modules/versioning/ruby, lib/util/cache/package,
lib/modules/versioning/maven, lib/modules/versioning/gradle) and then
raise the rule to "error". Note oxlint 1.72 also supports an
allowTypeAnnotation option if typed consts should remain arrows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(linter): enforce func-style at error

* refactor(func-style): convert const-arrow functions to declarations in with-cache spec and ruby versioning

* refactor(func-style): convert const-arrow functions to declarations in maven/gradle modules

* refactor(func-style): convert const-arrow functions to declarations in remaining versioning modules

* refactor(func-style): convert const-arrow functions to declarations in platform modules

Discriminated-union closures (ensureCommentRemoval byTopic/byContent
helpers in github, gitlab, bitbucket, bitbucket-server) needed the
narrowed field passed explicitly as a parameter, since function
declarations are hoisted and lose the outer if-narrowing that inline
arrow-function closures previously relied on.

* refactor(func-style): convert const-arrow functions to declarations across util/manager/datasource modules

One case (logger/renovate-logger.ts logOnceFn) is kept as an arrow
function with a documented oxlint-disable: it is stored on
this.logger.once[level] and relies on lexically-bound this, which a
hoisted function declaration would not preserve.

* refactor(func-style): convert remaining const-arrow test helpers to declarations

get-updated.spec.ts: hoisted expectPipCompilePackageAndLockFile to
module scope (it only closes over the module-level pipCompile mock)
and added a documented oxlint-disable for vitest/expect-expect on its
caller test, since oxlint's expect-expect check does not trace
assertions through an externally-defined helper function.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 11:14:22 +00:00

269 lines
8.6 KiB
TypeScript

import { DateTime, Settings } from 'luxon';
import * as memCache from '../../../cache/memory/index.ts';
import { clone } from '../../../clone.ts';
import type { Timestamp } from '../../../timestamp.ts';
import type {
GithubDatasourceItem,
GithubGraphqlCacheRecord,
} from '../types.ts';
import { GithubGraphqlMemoryCacheStrategy } from './memory-cache-strategy.ts';
// const isoTs = (t: string) => DateTime.fromJSDate(new Date(t)).toISO()!;
const hourMinRe = /T\d{2}:\d{2}$/;
const hourMinSecRe = /T\d{2}:\d{2}:\d{2}$/;
const hourMinSecMillisRe = /T\d{2}:\d{2}:\d{2}\.\d\d\d$/;
function isoTs(t: string) {
let iso = t.replace(' ', 'T');
if (hourMinSecMillisRe.test(iso)) {
iso = `${iso}Z`;
} else if (hourMinSecRe.test(iso)) {
iso = `${iso}.000Z`;
} else if (hourMinRe.test(iso)) {
iso = `${iso}:00.000Z`;
} else {
throw new Error(`Unrecognized date-time string. ${t}`);
}
return iso as Timestamp;
}
function mockTime(input: string): void {
const now = DateTime.fromISO(isoTs(input)).valueOf();
Settings.now = () => now;
}
type CacheRecord = GithubGraphqlCacheRecord<GithubDatasourceItem>;
describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => {
beforeEach(() => {
memCache.init();
});
it('resets old cache', async () => {
const items = {
'1': { version: '1', releaseTimestamp: isoTs('2020-01-01 10:00') },
};
const cacheRecord: CacheRecord = {
items,
createdAt: isoTs('2022-10-01 15:30'),
};
memCache.set(
'github-graphql-cache:_test-namespace:bar',
clone(cacheRecord),
);
// At this moment, cache is valid
let now = '2022-10-31 15:29:59';
mockTime(now);
let strategy = new GithubGraphqlMemoryCacheStrategy(
'_test-namespace',
'bar',
);
let isPaginationDone = await strategy.reconcile([items['1']]);
let res = await strategy.finalizeAndReturn();
expect(res).toEqual(Object.values(items));
expect(isPaginationDone).toBe(true);
expect(memCache.get('github-graphql-cache:_test-namespace:bar')).toEqual(
cacheRecord,
);
// One second later, the cache is invalid
now = '2022-10-31 15:30:00';
mockTime(now);
strategy = new GithubGraphqlMemoryCacheStrategy('_test-namespace', 'bar');
isPaginationDone = await strategy.reconcile([]);
res = await strategy.finalizeAndReturn();
expect(res).toEqual([]);
expect(isPaginationDone).toBe(false);
expect(memCache.get('github-graphql-cache:_test-namespace:bar')).toEqual(
cacheRecord,
);
});
it('reconciles old cache record with new items', async () => {
const oldItems = {
'1': { version: '1', releaseTimestamp: isoTs('2020-01-01 10:00') },
'2': { version: '2', releaseTimestamp: isoTs('2020-01-01 11:00') },
'3': { version: '3', releaseTimestamp: isoTs('2020-01-01 12:00') },
};
const cacheRecord: CacheRecord = {
items: oldItems,
createdAt: isoTs('2022-10-30 12:00'),
};
memCache.set(
'github-graphql-cache:_test-namespace:bar',
clone(cacheRecord),
);
const now = '2022-10-31 15:30';
mockTime(now);
const newItem = {
version: '4',
releaseTimestamp: isoTs('2022-10-15 18:00'),
};
const page = [newItem];
const strategy = new GithubGraphqlMemoryCacheStrategy(
'_test-namespace',
'bar',
);
const isPaginationDone = await strategy.reconcile(page);
const res = await strategy.finalizeAndReturn();
expect(res).toEqual([...Object.values(oldItems), newItem]);
expect(isPaginationDone).toBe(false);
expect(memCache.get('github-graphql-cache:_test-namespace:bar')).toEqual({
items: {
...oldItems,
'4': newItem,
},
createdAt: isoTs('2022-10-30 12:00'),
});
});
it('signals to stop pagination', async () => {
const oldItems = {
'1': { releaseTimestamp: isoTs('2020-01-01 10:00'), version: '1' },
'2': { releaseTimestamp: isoTs('2020-01-01 11:00'), version: '2' },
'3': { releaseTimestamp: isoTs('2020-01-01 12:00'), version: '3' },
};
const cacheRecord: CacheRecord = {
items: oldItems,
createdAt: isoTs('2022-10-30 12:00'),
};
memCache.set(
'github-graphql-cache:_test-namespace:bar',
clone(cacheRecord),
);
const now = '2022-10-31 15:30';
mockTime(now);
const page = [
...Object.values(oldItems),
{ version: '4', releaseTimestamp: isoTs('2022-10-15 18:00') },
].reverse();
const strategy = new GithubGraphqlMemoryCacheStrategy(
'_test-namespace',
'bar',
);
const isPaginationDone = await strategy.reconcile(page);
expect(isPaginationDone).toBe(true);
});
it('reconciles entire page', async () => {
const oldItems = {
'1': { releaseTimestamp: isoTs('2020-01-01 00:00'), version: '1' },
'2': { releaseTimestamp: isoTs('2020-01-01 01:00'), version: '2' },
'3': { releaseTimestamp: isoTs('2020-01-01 02:00'), version: '3' },
};
const cacheRecord: CacheRecord = {
items: oldItems,
createdAt: isoTs('2022-12-31 12:00'),
};
memCache.set(
'github-graphql-cache:_test-namespace:bar',
clone(cacheRecord),
);
const now = '2022-12-31 23:59';
mockTime(now);
const page = [
{ version: '1', releaseTimestamp: isoTs('2022-12-31 10:00') },
{ version: '2', releaseTimestamp: isoTs('2022-12-31 11:00') },
{ version: '3', releaseTimestamp: isoTs('2022-12-31 12:00') },
{ version: '4', releaseTimestamp: isoTs('2022-12-31 13:00') },
].reverse();
const strategy = new GithubGraphqlMemoryCacheStrategy(
'_test-namespace',
'bar',
);
const isPaginationDone = await strategy.reconcile(page);
expect(isPaginationDone).toBe(true);
expect(
memCache.get('github-graphql-cache:_test-namespace:bar'),
).toMatchObject({
items: {
'1': { releaseTimestamp: isoTs('2022-12-31 10:00') },
'2': { releaseTimestamp: isoTs('2022-12-31 11:00') },
'3': { releaseTimestamp: isoTs('2022-12-31 12:00') },
'4': { releaseTimestamp: isoTs('2022-12-31 13:00') },
},
});
});
it('detects removed packages', async () => {
const items = {
// stabilized
'0': { version: '0', releaseTimestamp: isoTs('2022-09-30 10:00') }, // to be preserved
'1': { version: '1', releaseTimestamp: isoTs('2022-10-01 10:00') }, // to be preserved
// not stabilized
'2': { version: '2', releaseTimestamp: isoTs('2022-10-02 10:00') },
'3': { version: '3', releaseTimestamp: isoTs('2022-10-03 10:00') }, // to be deleted
'4': { version: '4', releaseTimestamp: isoTs('2022-10-04 10:00') },
'5': { version: '5', releaseTimestamp: isoTs('2022-10-05 10:00') }, // to be deleted
'6': { version: '6', releaseTimestamp: isoTs('2022-10-06 10:00') },
'7': { version: '7', releaseTimestamp: isoTs('2022-10-07 10:00') }, // to be deleted
'8': { version: '8', releaseTimestamp: isoTs('2022-10-08 10:00') },
};
const cacheRecord: CacheRecord = {
items,
createdAt: isoTs('2022-10-30 12:00'),
};
memCache.set(
'github-graphql-cache:_test-namespace:bar',
clone(cacheRecord),
);
const now = '2022-10-31 15:30';
mockTime(now);
const page = [
items['1'],
items['2'],
items['4'],
items['6'],
items['8'],
].reverse();
const strategy = new GithubGraphqlMemoryCacheStrategy(
'_test-namespace',
'bar',
);
const isPaginationDone = await strategy.reconcile(page);
const res = await strategy.finalizeAndReturn();
expect(res).toEqual([
{ version: '0', releaseTimestamp: isoTs('2022-09-30 10:00') },
{ version: '1', releaseTimestamp: isoTs('2022-10-01 10:00') },
{ version: '2', releaseTimestamp: isoTs('2022-10-02 10:00') },
{ version: '4', releaseTimestamp: isoTs('2022-10-04 10:00') },
{ version: '6', releaseTimestamp: isoTs('2022-10-06 10:00') },
{ version: '8', releaseTimestamp: isoTs('2022-10-08 10:00') },
]);
expect(isPaginationDone).toBe(true);
expect(memCache.get('github-graphql-cache:_test-namespace:bar')).toEqual({
items: {
'0': { version: '0', releaseTimestamp: isoTs('2022-09-30 10:00') },
'1': { version: '1', releaseTimestamp: isoTs('2022-10-01 10:00') },
'2': { version: '2', releaseTimestamp: isoTs('2022-10-02 10:00') },
'4': { version: '4', releaseTimestamp: isoTs('2022-10-04 10:00') },
'6': { version: '6', releaseTimestamp: isoTs('2022-10-06 10:00') },
'8': { version: '8', releaseTimestamp: isoTs('2022-10-08 10:00') },
},
createdAt: isoTs('2022-10-30 12:00'),
});
});
});