feat: modernize dashboard widgets
This commit is contained in:
1 parent
1a5e083fd0
commit
6f2cd7fc2b
270 files changed
+10189
-3544
No files matched your search
@@ -0,0 +1,79 @@
|
||||
import { Response } from "undici";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
|
||||
|
||||
import { timetableGetTimetableRequestHandler, timetableSearchStationsRequestHandler } from "./timetable";
|
||||
import { normalizeTimetableBaseUrl, readBoundedTimetableJsonAsync } from "./timetable-url";
|
||||
|
||||
vi.mock("@homarr/core/infrastructure/http", () => ({ fetchWithTrustedCertificatesAsync: vi.fn() }));
|
||||
|
||||
const mockFetch = vi.mocked(fetchWithTrustedCertificatesAsync);
|
||||
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
|
||||
describe("normalizeTimetableBaseUrl", () => {
|
||||
it("normalizes an HTTP endpoint", () => {
|
||||
expect(normalizeTimetableBaseUrl("https://search.ch/")).toBe("https://search.ch");
|
||||
});
|
||||
|
||||
it.each(["file:///etc/passwd", "https://user:secret@example.com", "https://example.com?redirect=/admin"])(
|
||||
"rejects unsafe base URL %s",
|
||||
(url) => expect(() => normalizeTimetableBaseUrl(url)).toThrow(),
|
||||
);
|
||||
});
|
||||
|
||||
describe("timetable response bounds", () => {
|
||||
it("rejects a response once its byte budget is exceeded", async () => {
|
||||
const response = new Response(JSON.stringify({ value: "x".repeat(100) }));
|
||||
await expect(readBoundedTimetableJsonAsync(response, 32)).rejects.toThrow("exceeds 32 bytes");
|
||||
});
|
||||
|
||||
it("caps and caches station results and rejects redirects", async () => {
|
||||
mockFetch.mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify(
|
||||
Array.from({ length: 125 }, (_, index) => ({
|
||||
id: String(index),
|
||||
label: `Station ${index}`,
|
||||
iconclass: "train",
|
||||
})),
|
||||
),
|
||||
),
|
||||
);
|
||||
const input = { baseUrl: "http://timetable.internal", query: "bounded-stations" };
|
||||
|
||||
const first = await timetableSearchStationsRequestHandler.handler(input).getDataAsync();
|
||||
const second = await timetableSearchStationsRequestHandler.handler(input).getDataAsync();
|
||||
|
||||
expect(first.data).toHaveLength(100);
|
||||
expect(second.data).toHaveLength(100);
|
||||
expect(mockFetch).toHaveBeenCalledOnce();
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({ redirect: "error", bodyTimeout: 10_000 }),
|
||||
);
|
||||
});
|
||||
|
||||
it("never caches more departures than requested", async () => {
|
||||
mockFetch.mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
connections: Array.from({ length: 5 }, (_, index) => ({
|
||||
time: `2026-01-01T00:0${index}:00Z`,
|
||||
line: "1",
|
||||
color: "ffffff",
|
||||
terminal: { name: `Stop ${index}` },
|
||||
dep_delay: "0",
|
||||
})),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const result = await timetableGetTimetableRequestHandler
|
||||
.handler({ baseUrl: "http://timetable.internal", stationId: "bounded-departures", limit: 2 })
|
||||
.getDataAsync();
|
||||
|
||||
expect(result.data.entries).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user