fix: complete widget modernization review
This commit is contained in:
35 files changed
+621
-265
No files matched your search
@@ -0,0 +1,175 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { Session } from "@homarr/auth";
|
||||
import { createId } from "@homarr/common";
|
||||
import type { Database } from "@homarr/db";
|
||||
import { integrations, integrationUserPermissions, users } from "@homarr/db/schema";
|
||||
import { createDb } from "@homarr/db/test";
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
import type { MediaRequest, MediaRequestStats } from "@homarr/integrations/types";
|
||||
import type * as MediaRequestListHandlerModule from "@homarr/request-handler/media-request-list";
|
||||
|
||||
import { calendarRouter } from "../../widgets/calendar";
|
||||
import { mediaRequestsRouter } from "../../widgets/media-requests";
|
||||
import { mediaServerRouter } from "../../widgets/media-server";
|
||||
|
||||
vi.hoisted(() => {
|
||||
process.env.SKIP_ENV_VALIDATION = "true";
|
||||
});
|
||||
|
||||
const createHandler = <T>(integration: { name: string }, data: T) => ({
|
||||
getDataAsync: async () => {
|
||||
if (integration.name === "Offline") throw new Error("offline");
|
||||
return { data, timestamp: new Date() };
|
||||
},
|
||||
});
|
||||
|
||||
vi.mock("@homarr/request-handler/calendar", () => ({
|
||||
calendarMonthRequestHandler: { handler: (integration: { name: string }) => createHandler(integration, []) },
|
||||
}));
|
||||
|
||||
vi.mock("@homarr/request-handler/media-server", () => ({
|
||||
mediaServerRequestHandler: { handler: (integration: { name: string }) => createHandler(integration, []) },
|
||||
}));
|
||||
|
||||
vi.mock("@homarr/request-handler/media-request-list", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof MediaRequestListHandlerModule>();
|
||||
return {
|
||||
...actual,
|
||||
mediaRequestListRequestHandler: {
|
||||
handler: (integration: { name: string }) =>
|
||||
createHandler(integration, [
|
||||
{
|
||||
id: 42,
|
||||
name: "Healthy request",
|
||||
type: "movie",
|
||||
backdropImageUrl: "https://example.com/backdrop.jpg",
|
||||
posterImagePath: "https://example.com/poster.jpg",
|
||||
href: "https://example.com/request/42",
|
||||
createdAt: new Date("2026-08-01T12:00:00Z"),
|
||||
status: "pending",
|
||||
availability: "requested",
|
||||
} satisfies MediaRequest,
|
||||
]),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@homarr/request-handler/media-request-stats", () => ({
|
||||
mediaRequestStatsRequestHandler: {
|
||||
handler: (integration: { name: string }) =>
|
||||
createHandler(integration, {
|
||||
stats: { total: 0, movie: 0, tv: 0, pending: 0, approved: 0, declined: 0, processing: 0, available: 0 },
|
||||
users: [
|
||||
{
|
||||
id: 7,
|
||||
displayName: "Healthy user",
|
||||
avatar: "https://example.com/avatar.jpg",
|
||||
requestCount: 3,
|
||||
link: "https://example.com/user/7",
|
||||
},
|
||||
],
|
||||
} satisfies MediaRequestStats),
|
||||
},
|
||||
}));
|
||||
|
||||
const createSession = (userId: string): Session => ({
|
||||
user: { id: userId, permissions: [], colorScheme: "light" },
|
||||
expires: new Date().toISOString(),
|
||||
});
|
||||
|
||||
const setupAsync = async (kind: IntegrationKind) => {
|
||||
const db = createDb();
|
||||
const userId = createId();
|
||||
const onlineId = createId();
|
||||
const offlineId = createId();
|
||||
await db.insert(users).values({ id: userId });
|
||||
await db.insert(integrations).values([
|
||||
{ id: onlineId, kind, name: "Online", url: "https://online.example.com" },
|
||||
{ id: offlineId, kind, name: "Offline", url: "https://offline.example.com" },
|
||||
]);
|
||||
await db.insert(integrationUserPermissions).values([
|
||||
{ integrationId: onlineId, userId, permission: "use" },
|
||||
{ integrationId: offlineId, userId, permission: "use" },
|
||||
]);
|
||||
return {
|
||||
db: db as Database,
|
||||
session: createSession(userId),
|
||||
integrationIds: [onlineId, offlineId],
|
||||
onlineId,
|
||||
offlineId,
|
||||
};
|
||||
};
|
||||
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
|
||||
describe("partial integration failures", () => {
|
||||
test("calendar and media server retain a failed integration record", async () => {
|
||||
const calendar = await setupAsync("sonarr");
|
||||
const calendarResults = await calendarRouter
|
||||
.createCaller({ db: calendar.db, deviceType: undefined, session: calendar.session })
|
||||
.findAllEvents({
|
||||
integrationIds: calendar.integrationIds,
|
||||
year: 2026,
|
||||
month: 7,
|
||||
releaseType: [],
|
||||
showUnmonitored: false,
|
||||
});
|
||||
expect(calendarResults).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ integration: expect.objectContaining({ id: calendar.offlineId }), error: "offline" }),
|
||||
]),
|
||||
);
|
||||
|
||||
const mediaServer = await setupAsync("plex");
|
||||
const streamResults = await mediaServerRouter
|
||||
.createCaller({ db: mediaServer.db, deviceType: undefined, session: mediaServer.session })
|
||||
.getCurrentStreams({ integrationIds: mediaServer.integrationIds, showOnlyPlaying: false });
|
||||
expect(streamResults).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ integrationId: mediaServer.offlineId, sessions: [], error: "offline" }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test("media request responses expose failed integrations without dropping successful data", async () => {
|
||||
const mediaRequests = await setupAsync("overseerr");
|
||||
const caller = mediaRequestsRouter.createCaller({
|
||||
db: mediaRequests.db,
|
||||
deviceType: undefined,
|
||||
session: mediaRequests.session,
|
||||
});
|
||||
|
||||
const latest = await caller.getLatestRequests({
|
||||
integrationIds: mediaRequests.integrationIds,
|
||||
statuses: ["pending"],
|
||||
recentDays: 0,
|
||||
});
|
||||
expect(latest.requests).toEqual([
|
||||
expect.objectContaining({
|
||||
id: 42,
|
||||
name: "Healthy request",
|
||||
integrationId: mediaRequests.onlineId,
|
||||
integration: expect.objectContaining({ id: mediaRequests.onlineId }),
|
||||
}),
|
||||
]);
|
||||
expect(latest.failedIntegrations).toEqual([
|
||||
expect.objectContaining({ integrationId: mediaRequests.offlineId, error: "offline" }),
|
||||
]);
|
||||
|
||||
const stats = await caller.getStats({ integrationIds: mediaRequests.integrationIds });
|
||||
expect(stats.failedIntegrations).toEqual([
|
||||
expect.objectContaining({ integrationId: mediaRequests.offlineId, error: "offline" }),
|
||||
]);
|
||||
expect(stats.stats).toHaveLength(1);
|
||||
expect(stats.users).toEqual([
|
||||
expect.objectContaining({
|
||||
id: 7,
|
||||
displayName: "Healthy user",
|
||||
integration: expect.objectContaining({ id: mediaRequests.onlineId }),
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user