Skip to content

Commit 5205f7b

Browse files
soujaypoornimanayarkentdrcquirosj
authored
Add licensing test for expiring and expired licenses (#2227)
* Acceptance tests for license tab in configuration Co-authored-by: Poornima Nayar <[email protected]> Co-authored-by: Dan Kent <[email protected]> Co-authored-by: Christian <[email protected]>
1 parent b7ba138 commit 5205f7b

File tree

15 files changed

+270
-152
lines changed

15 files changed

+270
-152
lines changed

src/Frontend/src/components/configuration/PlatformLicense.vue

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,36 +35,44 @@ const configuration = useConfiguration();
3535
<div class="box">
3636
<div class="row">
3737
<div class="license-info">
38-
<div><b>Platform license type:</b> {{ typeText(license, configuration) }}{{ license.licenseEdition }}</div>
38+
<div>
39+
<b>Platform license type:</b> <span role="note" aria-label="license-type">{{ typeText(license, configuration) }}{{ license.licenseEdition }}</span>
40+
</div>
3941

4042
<template v-if="licenseStatus.isSubscriptionLicense">
4143
<div>
4244
<b>License expiry date: </b>
4345
<span
46+
role="note"
47+
aria-label="license-expiry-date"
4448
:class="{
4549
'license-expired': licenseStatus.isPlatformExpired,
4650
}"
4751
>
4852
{{ license.formattedExpirationDate }}
49-
{{ licenseStatus.subscriptionDaysLeft }}
53+
<span role="note" aria-label="license-days-left">{{ licenseStatus.subscriptionDaysLeft }}</span>
5054
<exclamation-mark :type="convertToWarningLevel(licenseStatus.warningLevel)" />
5155
</span>
52-
<div class="license-expired-text" v-if="licenseStatus.isPlatformExpired">Your license expired. Please update the license to continue using the Particular Service Platform.</div>
56+
<div class="license-expired-text" role="note" aria-label="license-expired" v-if="licenseStatus.isPlatformExpired">Your license expired. Please update the license to continue using the Particular Service Platform.</div>
5357
</div>
5458
</template>
5559
<template v-if="licenseStatus.isTrialLicense">
5660
<div>
5761
<b>License expiry date: </b>
5862
<span
63+
role="note"
64+
aria-label="license-expiry-date"
5965
:class="{
6066
'license-expired': licenseStatus.isPlatformTrialExpired,
6167
}"
6268
>
6369
{{ license.formattedExpirationDate }}
64-
{{ licenseStatus.trialDaysLeft }}
70+
<span role="note" aria-label="license-days-left"> {{ licenseStatus.trialDaysLeft }}</span>
6571
<exclamation-mark :type="convertToWarningLevel(licenseStatus.warningLevel)" />
6672
</span>
67-
<div class="license-expired-text" v-if="licenseStatus.isPlatformTrialExpired">Your license expired. To continue using the Particular Service Platform you'll need to extend your license.</div>
73+
<div class="license-expired-text" role="note" aria-label="license-expired" v-if="licenseStatus.isPlatformTrialExpired">
74+
Your license expired. To continue using the Particular Service Platform you'll need to extend your license.
75+
</div>
6876
<div class="license-page-extend-trial" v-if="licenseStatus.isPlatformTrialExpiring && licenseStatus.isPlatformTrialExpired">
6977
<a class="btn btn-default btn-primary" :href="license.license_extension_url" target="_blank">Extend your license&nbsp;&nbsp;<i class="fa fa-external-link"></i></a>
7078
</div>
@@ -75,16 +83,18 @@ const configuration = useConfiguration();
7583
<span>
7684
<b>Upgrade protection expiry date:</b>
7785
<span
86+
role="note"
87+
aria-label="license-expiry-date"
7888
:class="{
7989
'license-expired': licenseStatus.isInvalidDueToUpgradeProtectionExpired,
8090
}"
8191
>
8292
{{ license.formattedUpgradeProtectionExpiration }}
83-
{{ licenseStatus.upgradeDaysLeft }}
93+
<span role="note" aria-label="license-days-left">{{ licenseStatus.upgradeDaysLeft }}</span>
8494
<exclamation-mark :type="convertToWarningLevel(licenseStatus.warningLevel)" />
8595
</span>
8696
</span>
87-
<div class="license-expired-text" v-if="licenseStatus.isValidWithExpiredUpgradeProtection || licenseStatus.isValidWithExpiringUpgradeProtection">
97+
<div class="license-expired-text" role="note" aria-label="license-expired" v-if="licenseStatus.isValidWithExpiredUpgradeProtection || licenseStatus.isValidWithExpiringUpgradeProtection">
8898
<b>Warning:</b> Once upgrade protection expires, you'll no longer have access to support or new product versions.
8999
</div>
90100
<div class="license-expired-text" v-if="licenseStatus.isInvalidDueToUpgradeProtectionExpired">Your license upgrade protection expired before this version of ServicePulse was released.</div>

src/Frontend/src/resources/LicenseInfo.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,8 @@ export enum LicenseStatus {
3030
ValidWithExpiringUpgradeProtection = "ValidWithExpiringUpgradeProtection",
3131
ValidWithExpiringSubscription = "ValidWithExpiringSubscription",
3232
}
33+
export enum LicenseType {
34+
Subscription,
35+
Trial,
36+
UpgradeProtection,
37+
}

src/Frontend/src/views/ConfigurationView.vue

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,35 +53,37 @@ function preventIfDisabled(e: Event) {
5353
<div class="row">
5454
<div class="col-sm-12">
5555
<div class="nav tabs">
56-
<h5 :class="{ active: isRouteSelected(routeLinks.configuration.license.link), disabled: notConnected }" @click.capture="preventIfDisabled" class="nav-item">
56+
<h5 :class="{ active: isRouteSelected(routeLinks.configuration.license.link), disabled: notConnected }" @click.capture="preventIfDisabled" class="nav-item" role="tab" aria-label="license">
5757
<RouterLink :to="routeLinks.configuration.license.link">License</RouterLink>
5858
<exclamation-mark :type="convertToWarningLevel(licenseStatus.warningLevel)" />
5959
</h5>
6060
<h5
6161
:class="{ active: isRouteSelected(routeLinks.throughput.setup.root) || isRouteSelected(routeLinks.throughput.setup.mask.link) || isRouteSelected(routeLinks.throughput.setup.diagnostics.link), disabled: notConnected }"
6262
@click.capture="preventIfDisabled"
6363
class="nav-item"
64+
role="tab"
65+
aria-label="usage-setup"
6466
>
6567
<RouterLink :to="routeLinks.throughput.setup.root">Usage Setup</RouterLink>
6668
<exclamation-mark :type="WarningLevel.Danger" v-if="hasErrors" />
6769
</h5>
6870
<template v-if="!licenseStatus.isExpired">
69-
<h5 :class="{ active: isRouteSelected(routeLinks.configuration.massTransitConnector.link), disabled: notConnected }" @click.capture="preventIfDisabled" class="nav-item">
71+
<h5 :class="{ active: isRouteSelected(routeLinks.configuration.massTransitConnector.link), disabled: notConnected }" @click.capture="preventIfDisabled" class="nav-item" role="tab" aria-label="mass-transit-connector">
7072
<RouterLink :to="routeLinks.configuration.massTransitConnector.link">MassTransit Connector</RouterLink>
7173
</h5>
72-
<h5 :class="{ active: isRouteSelected(routeLinks.configuration.healthCheckNotifications.link), disabled: notConnected }" @click.capture="preventIfDisabled" class="nav-item">
74+
<h5 :class="{ active: isRouteSelected(routeLinks.configuration.healthCheckNotifications.link), disabled: notConnected }" @click.capture="preventIfDisabled" class="nav-item" role="tab" aria-label="health-check-notifications">
7375
<RouterLink :to="routeLinks.configuration.healthCheckNotifications.link">Health Check Notifications</RouterLink>
7476
</h5>
75-
<h5 :class="{ active: isRouteSelected(routeLinks.configuration.retryRedirects.link), disabled: notConnected }" @click.capture="preventIfDisabled" class="nav-item">
77+
<h5 :class="{ active: isRouteSelected(routeLinks.configuration.retryRedirects.link), disabled: notConnected }" @click.capture="preventIfDisabled" class="nav-item" role="tab" aria-label="retry-redirects">
7678
<RouterLink :to="routeLinks.configuration.retryRedirects.link">Retry Redirects ({{ redirectCount }})</RouterLink>
7779
</h5>
78-
<h5 :class="{ active: isRouteSelected(routeLinks.configuration.connections.link) }" class="nav-item">
80+
<h5 :class="{ active: isRouteSelected(routeLinks.configuration.connections.link) }" class="nav-item" role="tab" aria-label="connections">
7981
<RouterLink :to="routeLinks.configuration.connections.link">
8082
Connections
8183
<exclamation-mark v-if="displayConnectionsWarning" :type="WarningLevel.Danger" />
8284
</RouterLink>
8385
</h5>
84-
<h5 :class="{ active: isRouteSelected(routeLinks.configuration.endpointConnection.link), disabled: notConnected }" @click.capture="preventIfDisabled" class="nav-item">
86+
<h5 :class="{ active: isRouteSelected(routeLinks.configuration.endpointConnection.link), disabled: notConnected }" @click.capture="preventIfDisabled" class="nav-item" role="tab" aria-label="endpoint-connection">
8587
<RouterLink :to="routeLinks.configuration.endpointConnection.link">Endpoint Connection</RouterLink>
8688
</h5>
8789
</template>

src/Frontend/test/mocks/license-response-template.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/Frontend/test/preconditions/hasLicense.ts

Lines changed: 0 additions & 59 deletions
This file was deleted.

src/Frontend/test/preconditions/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export * from "../preconditions/hasLicense";
1+
export * from "../preconditions/licensing";
22
export { hasServiceControlMainInstance } from "../preconditions/hasServiceControlMainInstance";
33
export { hasServiceControlMonitoringInstance } from "../preconditions/hasServiceControlMonitoringInstance";
44
export { hasServiceControlMonitoringInstanceUrl } from "../preconditions/hasServiceControlMonitoringInstanceUrl";
@@ -15,6 +15,7 @@ export * from "./hasMonitoredEndpointDetails";
1515
export * from "../preconditions/hasHeartbeatEndpoints";
1616
export { serviceControlWithMonitoring } from "./serviceControlWithMonitoring";
1717
export * from "./recoverability";
18+
export * from "./licensing";
1819
export { hasLicensingReportAvailable } from "../preconditions/hasLicensingReportAvailable";
1920
export { hasLicensingSettingTest } from "../preconditions/hasLicensingSettingTest";
2021
export { hasLicensingEndpoints } from "../preconditions/hasLicensingEndpoints";
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { SetupFactoryOptions } from "../driver";
2+
import LicenseInfo, { LicenseStatus, LicenseType } from "@/resources/LicenseInfo";
3+
import { useLicense } from "@/composables/serviceLicense";
4+
5+
const { license } = useLicense();
6+
const licenseResponseTemplate = <LicenseInfo>{
7+
registered_to: "ACME Software",
8+
edition: "Enterprise",
9+
expiration_date: "2026-01-23T00:00:00.0000000Z",
10+
upgrade_protection_expiration: "",
11+
license_type: "Commercial",
12+
instance_name: "Particular.ServiceControl",
13+
trial_license: false,
14+
license_status: LicenseStatus.Valid,
15+
license_extension_url: "",
16+
status: "valid",
17+
};
18+
export const hasActiveLicense = ({ driver }: SetupFactoryOptions) => {
19+
const serviceControlInstanceUrl = window.defaultConfig.service_control_url;
20+
driver.mockEndpoint(`${serviceControlInstanceUrl}license`, {
21+
body: licenseResponseTemplate,
22+
});
23+
return licenseResponseTemplate;
24+
};
25+
export const hasExpiredLicense = (licenseType: LicenseType, expiredDays: number = 10, extensionUrl: string = "") => getLicenseMockedResponse(licenseType, expiredDays, extensionUrl, true);
26+
export const hasExpiringLicense = (licenseType: LicenseType, expiringInDays: number = 10, extensionUrl: string = "") => getLicenseMockedResponse(licenseType, expiringInDays, extensionUrl, false);
27+
28+
const getLicenseMockedResponse =
29+
(licenseType: LicenseType, expiringInDays: number, extensionUrl: string, isExpired: boolean) =>
30+
({ driver }: SetupFactoryOptions) => {
31+
const serviceControlInstanceUrl = window.defaultConfig.service_control_url;
32+
const customISOString = getCustomDateISOString(expiringInDays, isExpired);
33+
34+
let status: LicenseStatus;
35+
let trialLicense = false;
36+
let upgradeProtectionExpiration = "";
37+
let expirationDate = "";
38+
let licenseExtensionUrl = extensionUrl;
39+
40+
switch (licenseType) {
41+
case LicenseType.Subscription:
42+
status = isExpired ? LicenseStatus.InvalidDueToExpiredSubscription : LicenseStatus.ValidWithExpiringSubscription;
43+
expirationDate = customISOString;
44+
break;
45+
case LicenseType.Trial:
46+
status = isExpired ? LicenseStatus.InvalidDueToExpiredTrial : LicenseStatus.ValidWithExpiringTrial;
47+
expirationDate = customISOString;
48+
trialLicense = true;
49+
licenseExtensionUrl = extensionUrl ? extensionUrl : "https://particular.net/extend-your-trial?p=servicepulse";
50+
break;
51+
case LicenseType.UpgradeProtection:
52+
status = isExpired ? LicenseStatus.InvalidDueToExpiredUpgradeProtection : LicenseStatus.ValidWithExpiringUpgradeProtection;
53+
upgradeProtectionExpiration = customISOString;
54+
licenseExtensionUrl = extensionUrl ? extensionUrl : "https://particular.net/extend-your-trial?p=servicepulse";
55+
break;
56+
}
57+
58+
//We need to reset the global state to ensure the warning toast is always triggered by the value changing between multiple test runs. See documented issue and proposed solution https://github.com/Particular/ServicePulse/issues/1905
59+
license.license_status = LicenseStatus.Unavailable;
60+
61+
const response = {
62+
...licenseResponseTemplate,
63+
license_type: status === LicenseStatus.ValidWithExpiringTrial || status === LicenseStatus.InvalidDueToExpiredTrial ? "Trial" : "Commercial",
64+
trial_license: trialLicense,
65+
expiration_date: expirationDate,
66+
upgrade_protection_expiration: upgradeProtectionExpiration,
67+
license_status: status,
68+
license_extension_url: licenseExtensionUrl,
69+
};
70+
console.log(response);
71+
driver.mockEndpoint(`${serviceControlInstanceUrl}license`, {
72+
body: response,
73+
});
74+
return response;
75+
};
76+
77+
function getCustomDateISOString(daysCount: number, isExpired: boolean) {
78+
const today = new Date();
79+
const customDate = new Date(today);
80+
81+
if (isExpired) {
82+
customDate.setDate(today.getDate() - daysCount);
83+
} else {
84+
customDate.setDate(today.getDate() + daysCount);
85+
}
86+
87+
const nativeISOString = customDate.toISOString(); // e.g., "2026-02-02T14:23:45.123Z"
88+
const customISOString = nativeISOString.replace(/\.\d+Z$/, (match) => match.slice(0, -1).padEnd(8, "0") + "Z");
89+
return customISOString;
90+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { screen } from "@testing-library/vue";
2+
3+
export async function licenseExpired() {
4+
const licenseExpiredText = await screen.findByRole("note", { name: "license-expired" });
5+
return licenseExpiredText.textContent?.trim();
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { screen } from "@testing-library/vue";
2+
3+
export async function licenseExpiryDate() {
4+
const licenseExpiryDateElement = await screen.findByRole("note", { name: "license-expiry-date" });
5+
return licenseExpiryDateElement;
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { screen } from "@testing-library/vue";
2+
3+
export async function licenseExpiryDaysLeft() {
4+
const licenseExpiryDaysLeftElement = await screen.findByRole("note", { name: "license-days-left" });
5+
return licenseExpiryDaysLeftElement;
6+
}

0 commit comments

Comments
 (0)