Skip to content

Commit f666dc6

Browse files
FrankLiu4138claude
andcommitted
refactor(notification): extract pure functions for testability
Extract getExtensionState() and buildNotificationContent() as exported pure functions that can be unit tested without VS Code API mocking. Add unit tests for both functions. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
1 parent 5b1a277 commit f666dc6

2 files changed

Lines changed: 145 additions & 28 deletions

File tree

src/upgrade/display/notificationManager.ts

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,62 @@ function getNowTs() {
2929
return Number(new Date()) / 1000;
3030
}
3131

32+
export type ExtensionState = "up-to-date" | "outdated" | "not-installed";
33+
34+
export interface NotificationContent {
35+
message: string;
36+
upgradeButtonText: string;
37+
fixCVEButtonText: string;
38+
}
39+
40+
export function getExtensionState(extensionVersion: string | undefined): ExtensionState {
41+
if (!extensionVersion) {
42+
return "not-installed";
43+
}
44+
if (semver.gte(extensionVersion, Upgrade.MIN_APPMOD_VERSION)) {
45+
return "up-to-date";
46+
}
47+
return "outdated";
48+
}
49+
50+
export function buildNotificationContent(
51+
issues: UpgradeIssue[],
52+
extensionState: ExtensionState,
53+
): NotificationContent {
54+
const cveIssues = issues.filter(
55+
(i): i is CveUpgradeIssue => i.reason === UpgradeReason.CVE
56+
);
57+
const nonCVEIssues = issues.filter(
58+
(i) => i.reason !== UpgradeReason.CVE
59+
);
60+
const hasCVEIssue = cveIssues.length > 0;
61+
const isReady = extensionState === "up-to-date";
62+
63+
const message = hasCVEIssue
64+
? buildCVENotificationMessage(cveIssues, isReady)
65+
: buildNotificationMessage(nonCVEIssues[0], isReady);
66+
67+
let upgradeButtonText: string;
68+
let fixCVEButtonText: string;
69+
70+
switch (extensionState) {
71+
case "up-to-date":
72+
upgradeButtonText = BUTTON_TEXT_UPGRADE;
73+
fixCVEButtonText = BUTTON_TEXT_FIX_CVE;
74+
break;
75+
case "outdated":
76+
upgradeButtonText = BUTTON_TEXT_UPDATE_AND_UPGRADE;
77+
fixCVEButtonText = BUTTON_TEXT_UPDATE_AND_FIX_CVE;
78+
break;
79+
case "not-installed":
80+
upgradeButtonText = BUTTON_TEXT_INSTALL_AND_UPGRADE;
81+
fixCVEButtonText = BUTTON_TEXT_INSTALL_AND_FIX_CVE;
82+
break;
83+
}
84+
85+
return { message, upgradeButtonText, fixCVEButtonText };
86+
}
87+
3288
class NotificationManager implements IUpgradeIssuesRenderer {
3389
private hasShown = false;
3490
private context?: ExtensionContext;
@@ -45,16 +101,6 @@ class NotificationManager implements IUpgradeIssuesRenderer {
45101
return;
46102
}
47103

48-
// Filter to only CVE issues and cast to CveUpgradeIssue[]
49-
const cveIssues = issues.filter(
50-
(i): i is CveUpgradeIssue => i.reason === UpgradeReason.CVE
51-
);
52-
const nonCVEIssues = issues.filter(
53-
(i) => i.reason !== UpgradeReason.CVE
54-
);
55-
const hasCVEIssue = cveIssues.length > 0;
56-
const issue = hasCVEIssue ? cveIssues[0] : nonCVEIssues[0];
57-
58104
if (!this.shouldShow()) {
59105
return;
60106
}
@@ -65,35 +111,26 @@ class NotificationManager implements IUpgradeIssuesRenderer {
65111
this.hasShown = true;
66112

67113
const ext = extensions.getExtension(ExtensionName.APP_MODERNIZATION_UPGRADE_FOR_JAVA);
68-
const hasExtension = !!ext;
69-
const isExtensionUpToDate = hasExtension
70-
&& !!ext.packageJSON?.version
71-
&& semver.gte(ext.packageJSON.version, Upgrade.MIN_APPMOD_VERSION);
72-
const prompt = buildFixPrompt(issue);
114+
const extensionState = getExtensionState(ext?.packageJSON?.version);
115+
const { message, upgradeButtonText, fixCVEButtonText } = buildNotificationContent(issues, extensionState);
73116

74-
let notificationMessage = "";
117+
const hasCVEIssue = issues.some(i => i.reason === UpgradeReason.CVE);
118+
const issue = hasCVEIssue
119+
? issues.find((i): i is CveUpgradeIssue => i.reason === UpgradeReason.CVE)!
120+
: issues.find(i => i.reason !== UpgradeReason.CVE)!;
121+
const prompt = buildFixPrompt(issue);
75122

76-
if (hasCVEIssue) {
77-
notificationMessage = buildCVENotificationMessage(cveIssues, isExtensionUpToDate);
78-
} else {
79-
notificationMessage = buildNotificationMessage(issue, isExtensionUpToDate);
80-
}
81-
const upgradeButtonText = isExtensionUpToDate
82-
? BUTTON_TEXT_UPGRADE
83-
: hasExtension ? BUTTON_TEXT_UPDATE_AND_UPGRADE : BUTTON_TEXT_INSTALL_AND_UPGRADE;
84-
const fixCVEButtonText = isExtensionUpToDate
85-
? BUTTON_TEXT_FIX_CVE
86-
: hasExtension ? BUTTON_TEXT_UPDATE_AND_FIX_CVE : BUTTON_TEXT_INSTALL_AND_FIX_CVE;
87123
sendInfo(operationId, {
88124
operationName: "java.dependency.upgradeNotification.show",
125+
extensionState,
89126
});
90127

91128
const buttons = hasCVEIssue
92129
? [fixCVEButtonText, BUTTON_TEXT_NOT_NOW]
93130
: [upgradeButtonText, BUTTON_TEXT_NOT_NOW];
94131

95132
const selection = await window.showInformationMessage(
96-
notificationMessage,
133+
message,
97134
...buttons
98135
);
99136
sendInfo(operationId, {
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
import * as assert from "assert";
5+
import { getExtensionState, buildNotificationContent } from "../../src/upgrade/display/notificationManager";
6+
import { UpgradeReason, type UpgradeIssue } from "../../src/upgrade/type";
7+
import { Upgrade } from "../../src/constants";
8+
9+
suite("notificationManager pure functions", () => {
10+
11+
suite("getExtensionState", () => {
12+
test("returns 'not-installed' when version is undefined", () => {
13+
assert.strictEqual(getExtensionState(undefined), "not-installed");
14+
});
15+
16+
test("returns 'outdated' when version is below minimum", () => {
17+
assert.strictEqual(getExtensionState("1.0.0"), "outdated");
18+
});
19+
20+
test("returns 'up-to-date' when version equals minimum", () => {
21+
assert.strictEqual(getExtensionState(Upgrade.MIN_APPMOD_VERSION), "up-to-date");
22+
});
23+
24+
test("returns 'up-to-date' when version is above minimum", () => {
25+
assert.strictEqual(getExtensionState("99.0.0"), "up-to-date");
26+
});
27+
});
28+
29+
suite("buildNotificationContent", () => {
30+
const upgradeIssue: UpgradeIssue = {
31+
packageId: "org.springframework.boot:spring-boot-starter",
32+
packageDisplayName: "Spring Boot",
33+
currentVersion: "2.7.0",
34+
name: "spring-boot-starter",
35+
reason: UpgradeReason.DEPRECATED,
36+
suggestedVersion: { name: "3.2.0", description: "latest stable" },
37+
};
38+
39+
const cveIssue: UpgradeIssue = {
40+
packageId: "org.apache.logging.log4j:log4j-core",
41+
packageDisplayName: "Log4j",
42+
currentVersion: "2.14.0",
43+
name: "log4j-core",
44+
reason: UpgradeReason.CVE,
45+
suggestedVersion: { name: "2.17.1", description: "patched" },
46+
severity: "critical",
47+
description: "Remote code execution",
48+
link: "https://nvd.nist.gov/vuln/detail/CVE-2021-44228",
49+
};
50+
51+
test("shows 'Upgrade Now' when extension is up-to-date", () => {
52+
const result = buildNotificationContent([upgradeIssue], "up-to-date");
53+
assert.strictEqual(result.upgradeButtonText, "Upgrade Now");
54+
assert.strictEqual(result.fixCVEButtonText, "Fix Now");
55+
});
56+
57+
test("shows 'Install Extension and Upgrade' when extension is not installed", () => {
58+
const result = buildNotificationContent([upgradeIssue], "not-installed");
59+
assert.strictEqual(result.upgradeButtonText, "Install Extension and Upgrade");
60+
assert.strictEqual(result.fixCVEButtonText, "Install Extension and Fix");
61+
});
62+
63+
test("shows 'Update Extension and Upgrade' when extension is outdated", () => {
64+
const result = buildNotificationContent([upgradeIssue], "outdated");
65+
assert.strictEqual(result.upgradeButtonText, "Update Extension and Upgrade");
66+
assert.strictEqual(result.fixCVEButtonText, "Update Extension and Fix");
67+
});
68+
69+
test("builds CVE message when CVE issues present", () => {
70+
const result = buildNotificationContent([cveIssue], "up-to-date");
71+
assert.ok(result.message.includes("CVE"));
72+
assert.strictEqual(result.fixCVEButtonText, "Fix Now");
73+
});
74+
75+
test("builds upgrade message when no CVE issues", () => {
76+
const result = buildNotificationContent([upgradeIssue], "up-to-date");
77+
assert.ok(result.message.includes("Spring Boot"));
78+
});
79+
});
80+
});

0 commit comments

Comments
 (0)