fix(notifications): resolve organization id for self-hosted server threshold alerts

This commit is contained in:
detail-app[bot] 2026-09-04 02:49:27 +00:00 committed by GitHub
parent 1572008cdf
commit a9aeeef7f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 107 additions and 1 deletions

View File

@ -0,0 +1,95 @@
import { db } from "@dokploy/server/db";
import { getWebServerSettings } from "@dokploy/server/services/web-server-settings";
import { sendServerThresholdNotifications } from "@dokploy/server/utils/notifications/server-threshold";
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("@dokploy/server/services/web-server-settings", () => ({
getWebServerSettings: vi.fn(),
updateWebServerSettings: vi.fn(),
}));
vi.mock("@dokploy/server/utils/notifications/server-threshold", () => ({
sendServerThresholdNotifications: vi.fn(),
}));
import { notificationRouter } from "@/server/api/routers/notification";
const buildCaller = () =>
notificationRouter.createCaller({
session: null,
db,
req: {} as any,
res: {} as any,
user: null,
} as any);
const dokployInput = {
ServerType: "Dokploy" as const,
Type: "CPU" as const,
Value: 91.5,
Threshold: 80,
Message: "CPU usage (91.50%) exceeded threshold (80.00%)",
Timestamp: "2026-09-03T10:00:00.000Z",
Token: "server-token",
};
const memberFindFirst = () => db.query.member.findFirst as any;
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(getWebServerSettings).mockResolvedValue({
metricsConfig: { server: { token: "server-token" } },
} as any);
memberFindFirst().mockResolvedValue({
organizationId: "org-123",
organization: { id: "org-123" },
});
});
describe("receiveNotification — self-hosted Dokploy branch", () => {
it("resolves the admin's default organization and dispatches with a real organizationId", async () => {
const caller = buildCaller();
await caller.receiveNotification(dokployInput);
expect(sendServerThresholdNotifications).toHaveBeenCalledTimes(1);
expect(sendServerThresholdNotifications).toHaveBeenCalledWith(
"org-123",
expect.objectContaining({
ServerName: "Dokploy",
Type: "CPU",
Value: 91.5,
Threshold: 80,
Token: "server-token",
}),
);
});
it("resolves the default membership with the same query shape as auth.ts session resolution", async () => {
memberFindFirst().mockResolvedValue({
organizationId: "default-org",
organization: { id: "default-org" },
});
const caller = buildCaller();
await caller.receiveNotification(dokployInput);
expect(sendServerThresholdNotifications).toHaveBeenCalledWith(
"default-org",
expect.anything(),
);
expect(memberFindFirst()).toHaveBeenCalledTimes(1);
const queryOpts = memberFindFirst().mock.calls[0][0];
expect(queryOpts.orderBy).toHaveLength(2);
expect(queryOpts.with).toEqual({ organization: true });
});
it("throws and does not dispatch when no organization membership exists", async () => {
memberFindFirst().mockResolvedValue(undefined);
const caller = buildCaller();
await expect(
caller.receiveNotification(dokployInput),
).rejects.toMatchObject({ code: "BAD_REQUEST" });
expect(sendServerThresholdNotifications).not.toHaveBeenCalled();
});
});

View File

@ -89,6 +89,7 @@ import {
apiUpdateSlack,
apiUpdateTeams,
apiUpdateTelegram,
member,
notifications,
server,
} from "@/server/db/schema";
@ -517,7 +518,17 @@ export const notificationRouter = createTRPCRouter({
});
}
organizationId = "";
const activeMember = await db.query.member.findFirst({
orderBy: [desc(member.isDefault), desc(member.createdAt)],
with: { organization: true },
});
if (!activeMember?.organizationId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Organization not found",
});
}
organizationId = activeMember.organizationId;
ServerName = "Dokploy";
} else {
const result = await db