Remove unused service registry

This commit is contained in:
Alex Holliday committed 2025-12-16 21:12:58 +00:00
1 parent 837c4598ff
commit 4b1e8fb002
6 files changed
+333 -77

No files matched your search

-5
View File
@@ -19,7 +19,6 @@ import {
AuthService,
MonitorService,
QueueService,
ServiceRegistry,
TeamService,
RoleService,
TeamMemberService,
@@ -109,9 +108,5 @@ export const initServices = async () => {
stripeService,
};
Object.values(services).forEach((service) => {
ServiceRegistry.register(service.SERVICE_NAME, service);
});
return services;
};
-1
View File
@@ -22,7 +22,6 @@ export { default as StatusService } from "@/services/infrastructure/StatusServic
export { default as JobQueue } from "@/services/infrastructure/JobQueue.js";
export { default as JobGenerator } from "@/services/infrastructure/JobGenerator.js";
export { default as ServiceRegistry } from "@/services/system/ServiceRegistry.js";
export { default as SettingsService } from "@/services/system/SettingsService.js";
export { default as BillingService } from "@/services/system/BillingService.js";
export { default as StripeService } from "@/services/system/StripeService.js";
@@ -1,69 +0,0 @@
const SERVICE_NAME = "ServiceRegistry";
export interface IServiceRegistry {
register: (name: string, service: any) => void;
get: (name: string) => any;
listServices: () => string[];
}
class ServiceRegistry implements IServiceRegistry {
public SERVICE_NAME: string;
private static _instance: ServiceRegistry;
private services: { [key: string]: any };
private constructor() {
this.SERVICE_NAME = SERVICE_NAME;
this.services = {};
}
static get instance(): ServiceRegistry {
if (!this._instance) {
this._instance = new ServiceRegistry();
}
return this._instance;
}
get serviceName() {
return this.SERVICE_NAME;
}
// Instance methods
register(name: string, service: any) {
this.services[name] = service;
}
get(name: string) {
if (!this.services[name]) {
throw new Error(`Service ${name} is not registered`);
}
return this.services[name];
}
listServices() {
return Object.keys(this.services);
}
static get(name: string) {
if (!ServiceRegistry.instance) {
throw new Error("ServiceRegistry not initialized");
}
return ServiceRegistry.instance.get(name);
}
static register(name: string, service: any) {
if (!ServiceRegistry.instance) {
throw new Error("ServiceRegistry not initialized");
}
return ServiceRegistry.instance.register(name, service);
}
static listServices() {
if (!ServiceRegistry.instance) {
throw new Error("ServiceRegistry not initialized");
}
return ServiceRegistry.instance.listServices();
}
}
export default ServiceRegistry;
@@ -257,6 +257,106 @@ describe("StatusService.updateMonitorStats", () => {
expect(stats.avgResponseTime).toBeCloseTo((200 * 10 + 400) / 11);
expect(stats.uptimePercentage).toBeCloseTo(7 / 11);
expect(stats.lastResponseTime).toBe(400);
expect(stats.maxResponseTime).toBe(400);
});
expect(stats.maxResponseTime).toBe(400);
});
});
// Additional coverage: evaluateThresholds branches
describe("StatusService.evaluateThresholds", () => {
const service = new StatusService();
const buildCheck = (systemOverrides = {}) => ({
system: {
cpu: { usage_percent: 0.2, temperature: [40, 45] },
memory: { usage_percent: 0.3 },
disk: [{ usage_percent: 0.4 }],
...systemOverrides,
},
});
it("returns early when monitor is not infrastructure or not up", async () => {
const { monitor } = buildMonitor({ type: "http", status: "up", thresholds: { cpu: 50 } });
const check = buildCheck();
const res1 = await service.evaluateThresholds(monitor, check);
expect(res1).toEqual({ hasBreach: false, notes: [], metrics: expect.any(Object) });
const { monitor: m2 } = buildMonitor({ type: "infrastructure", status: "down", thresholds: { cpu: 50 } });
const res2 = await service.evaluateThresholds(m2, check);
expect(res2).toEqual({ hasBreach: false, notes: [], metrics: expect.any(Object) });
});
it("returns early when thresholds or system missing", async () => {
const { monitor } = buildMonitor({ type: "infrastructure", status: "up" });
const check = buildCheck();
const res1 = await service.evaluateThresholds(monitor, check);
expect(res1.hasBreach).toBe(false);
const { monitor: m2 } = buildMonitor({ type: "infrastructure", status: "up", thresholds: { cpu: 50 } });
const res2 = await service.evaluateThresholds(m2, { system: undefined });
expect(res2.hasBreach).toBe(false);
});
it("detects cpu/memory/disk/temperature breaches and fills notes", async () => {
const { monitor } = buildMonitor({
type: "infrastructure",
status: "up",
thresholds: { cpu: 50, memory: 60, disk: 80, temperature: 70 },
});
const check = buildCheck({
cpu: { usage_percent: 0.8, temperature: [60, 75] }, // 80% & 75C
memory: { usage_percent: 0.7 }, // 70%
disk: [{ usage_percent: 0.5 }, { usage_percent: 0.95 }], // max 95%
});
const res = await service.evaluateThresholds(monitor, check);
expect(res.hasBreach).toBe(true);
expect(res.notes).toEqual(
expect.arrayContaining([
"cpu 80% > 50%",
"memory 70% > 60%",
"disk 95.00% > 80%",
"temperature 75C > 70C",
])
);
expect(res.metrics.cpu).toMatchObject({ value: 80, threshold: 50, breached: true });
expect(res.metrics.memory).toMatchObject({ value: 70, threshold: 60, breached: true });
expect(res.metrics.disk).toMatchObject({ value: 95, threshold: 80, breached: true });
});
it("handles non-breach values and undefined fields safely", async () => {
const { monitor } = buildMonitor({
type: "infrastructure",
status: "up",
thresholds: { cpu: 95, memory: 95, disk: 99, temperature: 100 },
});
const check = buildCheck({
cpu: { usage_percent: undefined, temperature: [] },
memory: {},
disk: [{}, { usage_percent: 0.1 }], // max 10%
});
const res = await service.evaluateThresholds(monitor, check);
expect(res.hasBreach).toBe(false);
expect(res.notes).toEqual([]);
expect(res.metrics.cpu.breached).toBe(false);
expect(res.metrics.memory.breached).toBe(false);
expect(res.metrics.disk.breached).toBe(false);
});
it("returns safe default when unexpected error occurs", async () => {
const { monitor } = buildMonitor({
type: "infrastructure",
status: "up",
thresholds: { cpu: 50 },
});
// Create a system object that throws when accessed to exercise catch {}
const throwingSystem = new Proxy(
{},
{
get() {
throw new Error("boom");
},
}
);
const res = await service.evaluateThresholds(monitor, { system: throwingSystem });
expect(res).toEqual({ hasBreach: false, notes: [], metrics: expect.any(Object) });
});
});
@@ -0,0 +1,182 @@
import BillingService from "@/services/system/BillingService.js";
import ApiError from "@/utils/ApiError.js";
// Mock Org model
jest.mock("@/db/models/index.js", () => ({
Org: { findById: jest.fn(), findOne: jest.fn() },
}));
import { Org } from "@/db/models/index.js";
// Capture a shared mock Stripe client instance
jest.mock("stripe", () => {
const mockClient = {
prices: { list: jest.fn() },
customers: { create: jest.fn() },
checkout: { sessions: { create: jest.fn() } },
billingPortal: { sessions: { create: jest.fn() } },
subscriptions: { retrieve: jest.fn() },
};
const MockStripe = jest.fn(() => mockClient);
MockStripe.__mockClient = mockClient;
return { __esModule: true, default: MockStripe };
});
import Stripe from "stripe";
describe("BillingService", () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe("listPlans", () => {
it("lists plans in order and maps prices by lookup_key", async () => {
(Stripe.__mockClient).prices.list.mockResolvedValue({
data: [
{ lookup_key: "free", unit_amount: 0 },
{ lookup_key: "pro", unit_amount: 1000 },
{ lookup_key: "business", unit_amount: 2500 },
{ lookup_key: "enterprise", unit_amount: 9999 },
],
});
const svc = new BillingService();
const plans = await svc.listPlans();
expect(plans.map((p) => p.plan)).toEqual([
"free",
"pro",
"business",
"enterprise",
]);
expect(plans.find((p) => p.plan === "pro")?.price).toBe(1000);
expect(plans.find((p) => p.plan === "enterprise")?.price).toBe(9999);
});
});
describe("subscribePlan", () => {
it("throws when planKey missing", async () => {
const svc = new BillingService();
await expect(svc.subscribePlan("e@x.com", "org1", undefined)).rejects.toMatchObject({
message: "planKey is required",
status: 400,
});
});
it("throws when planKey invalid", async () => {
const svc = new BillingService();
await expect(svc.subscribePlan("e@x.com", "org1", "bogus"))
.rejects.toMatchObject({ message: "Invalid planKey", status: 400 });
});
it("throws when org not found", async () => {
Org.findById.mockResolvedValue(null);
const svc = new BillingService();
await expect(svc.subscribePlan("e@x.com", "org1", "pro"))
.rejects.toMatchObject({ message: "Organization not found", status: 404 });
});
it("creates customer and new subscription checkout when none exists", async () => {
const org = { _id: "org1", save: jest.fn(), billingCustomerId: null, subscriptionId: null };
Org.findById.mockResolvedValue(org);
(Stripe.__mockClient).customers.create.mockResolvedValue({ id: "cus_123" });
(Stripe.__mockClient).prices.list.mockResolvedValue({ data: [{ id: "price_123", lookup_key: "pro" }] });
(Stripe.__mockClient).checkout.sessions.create.mockResolvedValue({ url: "https://checkout" });
const svc = new BillingService();
const url = await svc.subscribePlan("e@x.com", "org1", "pro");
expect(org.billingCustomerId).toBe("cus_123");
expect(org.save).toHaveBeenCalled();
expect(Stripe.__mockClient.checkout.sessions.create).toHaveBeenCalled();
expect(url).toBe("https://checkout");
});
it("updates existing subscription via billing portal when subscriptionId exists", async () => {
const org = { _id: "org1", save: jest.fn(), billingCustomerId: "cus_123", subscriptionId: "sub_1" };
Org.findById.mockResolvedValue(org);
(Stripe.__mockClient).prices.list.mockResolvedValue({ data: [{ id: "price_123", lookup_key: "pro" }] });
(Stripe.__mockClient).billingPortal.sessions.create.mockResolvedValue({ url: "https://portal" });
const svc = new BillingService();
const url = await svc.subscribePlan("e@x.com", "org1", "pro");
expect(Stripe.__mockClient.billingPortal.sessions.create).toHaveBeenCalledWith(
expect.objectContaining({ customer: "cus_123" })
);
expect(url).toBe("https://portal");
});
it("throws when no price found for plan", async () => {
const org = { _id: "org1", save: jest.fn(), billingCustomerId: "cus_123", subscriptionId: null };
Org.findById.mockResolvedValue(org);
(Stripe.__mockClient).prices.list.mockResolvedValue({ data: [] });
const svc = new BillingService();
await expect(svc.subscribePlan("e@x.com", "org1", "pro"))
.rejects.toMatchObject({ message: "Price not found for the selected plan", status: 404 });
});
});
describe("cancelSubscription", () => {
it("throws when org not found", async () => {
Org.findById.mockResolvedValue(null);
const svc = new BillingService();
await expect(svc.cancelSubscription("org1"))
.rejects.toMatchObject({ message: "Organization not found", status: 404 });
});
it("throws when no active subscription found", async () => {
Org.findById.mockResolvedValue({ _id: "org1", subscriptionId: null });
const svc = new BillingService();
await expect(svc.cancelSubscription("org1"))
.rejects.toMatchObject({ message: "No active subscription found", status: 400 });
});
it("returns billing portal url on success", async () => {
Org.findById.mockResolvedValue({ _id: "org1", billingCustomerId: "cus_1", subscriptionId: "sub_1" });
(Stripe.__mockClient).billingPortal.sessions.create.mockResolvedValue({ url: "https://cancel" });
const svc = new BillingService();
const url = await svc.cancelSubscription("org1");
expect(url).toBe("https://cancel");
});
it("throws when billing portal session lacks url", async () => {
Org.findById.mockResolvedValue({ _id: "org1", billingCustomerId: "cus_1", subscriptionId: "sub_1" });
(Stripe.__mockClient).billingPortal.sessions.create.mockResolvedValue({ url: null });
const svc = new BillingService();
await expect(svc.cancelSubscription("org1"))
.rejects.toMatchObject({ message: "Failed to create billing portal session", status: 500 });
});
});
describe("confirmPlan", () => {
it("throws when org not found", async () => {
Org.findOne.mockResolvedValue(null);
const svc = new BillingService();
await expect(svc.confirmPlan("org1", "pro"))
.rejects.toMatchObject({ message: "Organization not found", status: 404 });
});
it("returns true only when expected is free if no subscriptionId", async () => {
Org.findOne.mockResolvedValue({ _id: "org1", subscriptionId: null });
const svc = new BillingService();
await expect(svc.confirmPlan("org1", "free")).resolves.toBe(true);
await expect(svc.confirmPlan("org1", "pro")).resolves.toBe(false);
});
it("returns true when active plan equals org.planKey", async () => {
Org.findOne.mockResolvedValue({ _id: "org1", subscriptionId: "sub1", planKey: "business" });
(Stripe.__mockClient).subscriptions.retrieve.mockResolvedValue({ items: { data: [{ price: { lookup_key: "business" } }] } });
const svc = new BillingService();
await expect(svc.confirmPlan("org1", "pro")).resolves.toBe(true);
});
it("returns true when active plan equals expectedPlanKey", async () => {
Org.findOne.mockResolvedValue({ _id: "org1", subscriptionId: "sub1", planKey: "pro" });
(Stripe.__mockClient).subscriptions.retrieve.mockResolvedValue({ items: { data: [{ price: { lookup_key: "enterprise" } }] } });
const svc = new BillingService();
await expect(svc.confirmPlan("org1", "enterprise")).resolves.toBe(true);
});
it("returns false when active plan differs from expected", async () => {
Org.findOne.mockResolvedValue({ _id: "org1", subscriptionId: "sub1", planKey: "pro" });
(Stripe.__mockClient).subscriptions.retrieve.mockResolvedValue({ items: { data: [{ price: { lookup_key: "business" } }] } });
const svc = new BillingService();
await expect(svc.confirmPlan("org1", "enterprise")).resolves.toBe(false);
});
});
});
@@ -0,0 +1,49 @@
import { EntitlementsFactory, SaaSEntitlementsProvider, SelfHostedEntitlementsProvider } from "@/services/system/EntitlementsService.js";
import { Org } from "@/db/models/index.js";
jest.mock("@/db/models/index.js", () => ({
Org: { findById: jest.fn() },
}));
describe("EntitlementsService", () => {
afterEach(() => jest.resetAllMocks());
test("SaaS provider maps plan keys to Plans", async () => {
const cases = [
{ planKey: "free", expected: "free" },
{ planKey: "pro", expected: "pro" },
{ planKey: "business", expected: "business" },
{ planKey: "enterprise", expected: "enterprise" },
{ planKey: undefined, expected: "free" },
];
for (const c of cases) {
Org.findById.mockReturnValueOnce({ lean: () => Promise.resolve({ planKey: c.planKey }) });
const provider = new SaaSEntitlementsProvider();
const ent = await provider.getForOrg("org1");
expect(ent.plan).toBe(c.expected);
}
});
test("Self-hosted provider returns unlimited plan", async () => {
const provider = new SelfHostedEntitlementsProvider();
const ent = await provider.getForOrg("org1");
expect(ent.plan).toBe("unlimited");
});
test("Factory returns correct provider based on DEPLOYMENT_MODE", () => {
jest.resetModules();
jest.isolateModules(() => {
jest.doMock("@/config/index.js", () => ({ config: { DEPLOYMENT_MODE: "self_hosted" } }));
const mod = require("@/services/system/EntitlementsService.js");
expect(mod.EntitlementsFactory.create()).toBeInstanceOf(mod.SelfHostedEntitlementsProvider);
});
jest.resetModules();
jest.isolateModules(() => {
jest.doMock("@/config/index.js", () => ({ config: { DEPLOYMENT_MODE: "saas" } }));
const mod = require("@/services/system/EntitlementsService.js");
expect(mod.EntitlementsFactory.create()).toBeInstanceOf(mod.SaaSEntitlementsProvider);
});
});
});