Skip to content

Commit 6460e6e

Browse files
viceicejamietanna
andauthored
fix(osv): ensure singleton database instance (#41512)
* fix(osv): ensure singleton database instance * Update lib/workers/repository/process/vulnerabilities.spec.ts Co-authored-by: Jamie Tanna <jamie.tanna@mend.io> --------- Co-authored-by: Jamie Tanna <jamie.tanna@mend.io>
1 parent f18e8c5 commit 6460e6e

File tree

2 files changed

+29
-9
lines changed

2 files changed

+29
-9
lines changed

lib/workers/repository/process/vulnerabilities.spec.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,15 @@ vi.mock('@renovatebot/osv-offline', () => {
2424

2525
describe('workers/repository/process/vulnerabilities', () => {
2626
describe('create()', () => {
27-
it('works', async () => {
27+
beforeEach(resetOsv);
28+
29+
it('works, and is a singleton', async () => {
30+
createMock.mockResolvedValue({
31+
getVulnerabilities: getVulnerabilitiesMock,
32+
});
33+
await expect(Vulnerabilities.create()).resolves.not.toThrow();
2834
await expect(Vulnerabilities.create()).resolves.not.toThrow();
35+
expect(createMock).toHaveBeenCalledTimes(1);
2936
});
3037

3138
it('throws when osv-offline error', async () => {
@@ -40,6 +47,7 @@ describe('workers/repository/process/vulnerabilities', () => {
4047
let vulnerabilities: Vulnerabilities;
4148

4249
beforeAll(async () => {
50+
resetOsv();
4351
createMock.mockResolvedValue({
4452
getVulnerabilities: getVulnerabilitiesMock,
4553
});
@@ -144,6 +152,7 @@ describe('workers/repository/process/vulnerabilities', () => {
144152
};
145153

146154
beforeAll(async () => {
155+
resetOsv();
147156
createMock.mockResolvedValue({
148157
getVulnerabilities: getVulnerabilitiesMock,
149158
});
@@ -1551,3 +1560,8 @@ describe('workers/repository/process/vulnerabilities', () => {
15511560
});
15521561
});
15531562
});
1563+
1564+
function resetOsv() {
1565+
// @ts-expect-error - reset the cached OSV client to avoid state leak between tests
1566+
Vulnerabilities.osvOffline = undefined;
1567+
}

lib/workers/repository/process/vulnerabilities.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ const { fromVector } = (_aeCvss as unknown as { default: typeof _aeCvss })
3535
.default;
3636

3737
export class Vulnerabilities {
38-
private osvOffline: OsvOffline | undefined;
38+
private static osvOffline: Promise<OsvOffline> | undefined;
39+
40+
private osvOffline: OsvOffline;
3941

4042
private static readonly datasourceEcosystemMap: Record<
4143
string,
@@ -53,17 +55,21 @@ export class Vulnerabilities {
5355
rubygems: 'RubyGems',
5456
};
5557

56-
private constructor() {
57-
// private constructor
58+
private constructor(osvOffline: OsvOffline) {
59+
this.osvOffline = osvOffline;
5860
}
5961

60-
private async initialize(): Promise<void> {
61-
this.osvOffline = await OsvOffline.create();
62+
private static initialize(): Promise<OsvOffline> {
63+
// no async here, so osv promise will only be created once
64+
Vulnerabilities.osvOffline ??= OsvOffline.create();
65+
return Vulnerabilities.osvOffline;
6266
}
6367

6468
static async create(): Promise<Vulnerabilities> {
65-
const instance = new Vulnerabilities();
66-
await instance.initialize();
69+
// intialize osv only once
70+
const osvOffline = await Vulnerabilities.initialize();
71+
72+
const instance = new Vulnerabilities(osvOffline);
6773
return instance;
6874
}
6975

@@ -180,7 +186,7 @@ export class Vulnerabilities {
180186
try {
181187
const osvVulnerabilities = await instrument(
182188
'get OSV vulnerabilities',
183-
() => this.osvOffline?.getVulnerabilities(ecosystem, packageName),
189+
() => this.osvOffline.getVulnerabilities(ecosystem, packageName),
184190
{
185191
attributes: {
186192
packageName,

0 commit comments

Comments
 (0)