mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 14:45:24 +00:00
feat: finish the homepage plugin, move service links and canvas out of core (B19)
This commit is contained in:
1 parent
21937a6b02
commit
d4ec444558
256 files changed
+27365
-9077
No files matched your search
@@ -0,0 +1,139 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import http from "node:http";
|
||||
import type { AddressInfo } from "node:net";
|
||||
import express from "express";
|
||||
|
||||
const state = vi.hoisted(() => ({
|
||||
unlocked: true,
|
||||
activity: [] as unknown[],
|
||||
recordResult: { status: "logged", id: 1 } as {
|
||||
status: string;
|
||||
id?: number;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/logger.js", () => ({
|
||||
dashboardLogger: { warn: vi.fn(), error: vi.fn(), success: vi.fn() },
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/auth-manager.js", () => ({
|
||||
AuthManager: {
|
||||
getInstance: () => ({
|
||||
createAuthMiddleware:
|
||||
() => (req: express.Request, _res: unknown, next: () => void) => {
|
||||
(req as unknown as { userId: string }).userId = "user-1";
|
||||
next();
|
||||
},
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/data-crypto.js", () => ({
|
||||
DataCrypto: {
|
||||
getUserDataKey: () => (state.unlocked ? "key" : null),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../../../database/repositories/factory.js", () => ({
|
||||
createCurrentRecentActivityRepository: () => ({
|
||||
listByUserId: async () => state.activity,
|
||||
deleteByUserId: async () => {},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../../../services/recent-activity.js", () => ({
|
||||
recordRecentActivity: async () => state.recordResult,
|
||||
}));
|
||||
|
||||
const { default: dashboardRoutes } =
|
||||
await import("../../../database/routes/dashboard-routes.js");
|
||||
|
||||
let server: http.Server;
|
||||
let baseUrl: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
state.unlocked = true;
|
||||
state.activity = [];
|
||||
state.recordResult = { status: "logged", id: 1 };
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use("/dashboard", dashboardRoutes);
|
||||
|
||||
server = http.createServer(app);
|
||||
await new Promise<void>((resolve) => server.listen(0, resolve));
|
||||
const { port } = server.address() as AddressInfo;
|
||||
baseUrl = `http://127.0.0.1:${port}`;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
});
|
||||
|
||||
describe("GET /dashboard/uptime", () => {
|
||||
it("returns a formatted uptime", async () => {
|
||||
const res = await fetch(`${baseUrl}/dashboard/uptime`);
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(body).toMatchObject({
|
||||
uptimeMs: expect.any(Number),
|
||||
formatted: expect.stringMatching(/^\d+d \d+h \d+m$/),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /dashboard/activity/recent", () => {
|
||||
it("401s when the user's data key is not unlocked", async () => {
|
||||
state.unlocked = false;
|
||||
const res = await fetch(`${baseUrl}/dashboard/activity/recent`);
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it("returns the user's recent activity", async () => {
|
||||
state.activity = [{ id: 1, type: "terminal" }];
|
||||
const res = await fetch(`${baseUrl}/dashboard/activity/recent`);
|
||||
expect(res.status).toBe(200);
|
||||
expect(await res.json()).toEqual(state.activity);
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /dashboard/activity/log", () => {
|
||||
it("rejects a body missing required fields", async () => {
|
||||
const res = await fetch(`${baseUrl}/dashboard/activity/log`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ type: "terminal" }),
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it("logs a valid activity", async () => {
|
||||
const res = await fetch(`${baseUrl}/dashboard/activity/log`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ type: "terminal", hostId: 1, hostName: "prod" }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
expect(await res.json()).toEqual({ message: "Activity logged", id: 1 });
|
||||
});
|
||||
|
||||
it("404s a host the user cannot access", async () => {
|
||||
state.recordResult = { status: "denied" };
|
||||
const res = await fetch(`${baseUrl}/dashboard/activity/log`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ type: "terminal", hostId: 1, hostName: "prod" }),
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DELETE /dashboard/activity/reset", () => {
|
||||
it("clears the user's recent activity", async () => {
|
||||
const res = await fetch(`${baseUrl}/dashboard/activity/reset`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
expect(await res.json()).toEqual({ message: "Recent activity cleared" });
|
||||
});
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
isValidServiceLinkUrl,
|
||||
normalizeServiceLinkUrl,
|
||||
} from "../../../database/routes/service-link-url.js";
|
||||
|
||||
describe("service link URL handling", () => {
|
||||
it("keeps explicit http and https URLs", () => {
|
||||
expect(normalizeServiceLinkUrl("https://example.com")).toBe(
|
||||
"https://example.com",
|
||||
);
|
||||
expect(normalizeServiceLinkUrl("http://192.168.1.10:8080")).toBe(
|
||||
"http://192.168.1.10:8080",
|
||||
);
|
||||
});
|
||||
|
||||
it("adds http to bare service addresses", () => {
|
||||
expect(normalizeServiceLinkUrl("192.168.1.10:8080")).toBe(
|
||||
"http://192.168.1.10:8080",
|
||||
);
|
||||
expect(normalizeServiceLinkUrl("termix.local")).toBe("http://termix.local");
|
||||
});
|
||||
|
||||
it("rejects unsupported schemes", () => {
|
||||
expect(isValidServiceLinkUrl("ssh://example.com")).toBe(false);
|
||||
expect(isValidServiceLinkUrl("javascript:alert(1)")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -34,8 +34,6 @@ describe("isValidEntityType", () => {
|
||||
"sshCredentials",
|
||||
"sshFolders",
|
||||
"vaultProfiles",
|
||||
"dashboardServiceLinks",
|
||||
"homepageItems",
|
||||
"userPreferences",
|
||||
]) {
|
||||
expect(isValidEntityType(type)).toBe(true);
|
||||
|
||||
Reference in new issue
Block a user