fix: complete widget review cleanup
This commit is contained in:
20 files changed
+251
-115
No files matched your search
@@ -28,7 +28,7 @@ describe("immichAlbumsRequestHandler", () => {
|
||||
});
|
||||
|
||||
test("sorts deterministically before caching a limited response", async () => {
|
||||
const getAlbumsAsync = vi.fn().mockResolvedValue(albums);
|
||||
const getAlbumsAsync = vi.fn().mockResolvedValue([...albums]);
|
||||
mocks.createIntegrationAsync.mockResolvedValue({ getAlbumsAsync });
|
||||
|
||||
const first = await immichAlbumsRequestHandler.handler(integration, { limit: 2 }).getDataAsync();
|
||||
@@ -40,7 +40,7 @@ describe("immichAlbumsRequestHandler", () => {
|
||||
});
|
||||
|
||||
test("preserves upstream order when no limit is requested", async () => {
|
||||
const getAlbumsAsync = vi.fn().mockResolvedValue(albums);
|
||||
const getAlbumsAsync = vi.fn().mockResolvedValue([...albums]);
|
||||
mocks.createIntegrationAsync.mockResolvedValue({ getAlbumsAsync });
|
||||
|
||||
const result = await immichAlbumsRequestHandler.handler(integration, {}).getDataAsync();
|
||||
|
||||
@@ -65,13 +65,30 @@ describe("timetable response bounds", () => {
|
||||
});
|
||||
|
||||
it("pins the vetted address into a direct connection without changing the request hostname", async () => {
|
||||
mockFetch.mockResolvedValue(
|
||||
new Response(JSON.stringify([{ id: "station", label: "Station", iconclass: "train" }])),
|
||||
);
|
||||
const pinnedAddresses = [
|
||||
{ address: "93.184.216.34", family: 4 as const },
|
||||
{ address: "2606:2800:220:1:248:1893:25c8:1946", family: 6 as const },
|
||||
];
|
||||
mockFetch.mockImplementation(async () => {
|
||||
const pinnedLookup = mockCreateCertificateAgent.mock.calls[0]?.[0]?.lookup;
|
||||
expect(pinnedLookup).toBeTypeOf("function");
|
||||
if (!pinnedLookup) throw new Error("Expected a pinned lookup function");
|
||||
|
||||
const lookupCallback = vi.fn();
|
||||
pinnedLookup("timetable.example.com", { all: true }, lookupCallback);
|
||||
expect(lookupCallback).toHaveBeenCalledWith(null, pinnedAddresses);
|
||||
|
||||
const repeatedLookupCallback = vi.fn();
|
||||
pinnedLookup("timetable.example.com", { all: true }, repeatedLookupCallback);
|
||||
expect(repeatedLookupCallback).toHaveBeenCalledWith(null, pinnedAddresses);
|
||||
expect(mockCloseDispatcher).not.toHaveBeenCalled();
|
||||
|
||||
const unexpectedHostCallback = vi.fn();
|
||||
pinnedLookup("different.example.com", { all: true }, unexpectedHostCallback);
|
||||
expect(unexpectedHostCallback.mock.calls[0]?.[0]).toMatchObject({ code: "ENOTFOUND" });
|
||||
|
||||
return new Response(JSON.stringify([{ id: "station", label: "Station", iconclass: "train" }]));
|
||||
});
|
||||
|
||||
await timetableSearchStationsRequestHandler
|
||||
.handler({
|
||||
@@ -98,21 +115,44 @@ describe("timetable response bounds", () => {
|
||||
expect.objectContaining({ dispatcher: mockDispatcher, redirect: "error" }),
|
||||
);
|
||||
|
||||
const pinnedLookup = mockCreateCertificateAgent.mock.calls[0]?.[0]?.lookup;
|
||||
expect(pinnedLookup).toBeTypeOf("function");
|
||||
if (!pinnedLookup) throw new Error("Expected a pinned lookup function");
|
||||
expect(mockCloseDispatcher).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
const lookupCallback = vi.fn();
|
||||
pinnedLookup("timetable.example.com", { all: true }, lookupCallback);
|
||||
expect(lookupCallback).toHaveBeenCalledWith(null, pinnedAddresses);
|
||||
it("pins timetable requests and closes their direct dispatcher", async () => {
|
||||
mockFetch.mockResolvedValue(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
connections: [
|
||||
{
|
||||
time: "2026-01-01T00:00:00Z",
|
||||
line: "1",
|
||||
color: "ffffff",
|
||||
terminal: { name: "Stop" },
|
||||
dep_delay: "0",
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
);
|
||||
const pinnedAddresses = [{ address: "93.184.216.34", family: 4 as const }];
|
||||
|
||||
const repeatedLookupCallback = vi.fn();
|
||||
pinnedLookup("timetable.example.com", { all: true }, repeatedLookupCallback);
|
||||
expect(repeatedLookupCallback).toHaveBeenCalledWith(null, pinnedAddresses);
|
||||
await timetableGetTimetableRequestHandler
|
||||
.handler({
|
||||
baseUrl: "https://timetable.example.com",
|
||||
stationId: "pinned-departures",
|
||||
limit: 1,
|
||||
pinnedAddresses,
|
||||
})
|
||||
.getDataAsync();
|
||||
|
||||
const unexpectedHostCallback = vi.fn();
|
||||
pinnedLookup("different.example.com", { all: true }, unexpectedHostCallback);
|
||||
expect(unexpectedHostCallback.mock.calls[0]?.[0]).toMatchObject({ code: "ENOTFOUND" });
|
||||
expect(mockCreateCertificateAgent).toHaveBeenCalledWith(
|
||||
{ lookup: expect.any(Function) },
|
||||
expect.objectContaining({ noProxy: "*" }),
|
||||
);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({ dispatcher: mockDispatcher, redirect: "error" }),
|
||||
);
|
||||
expect(mockCloseDispatcher).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
@@ -54,10 +54,14 @@ export const timetableSearchStationsRequestHandler = createRequestHandler<
|
||||
|
||||
export const timetableGetTimetableRequestHandler = createRequestHandler<
|
||||
Timetable,
|
||||
{ baseUrl: string; stationId: string; limit: number }
|
||||
{ baseUrl: string; stationId: string; limit: number; pinnedAddresses?: TimetableResolvedAddress[] }
|
||||
>({
|
||||
async requestAsync(input) {
|
||||
return await getTimetableAsync(input.baseUrl, { stationId: input.stationId, limit: input.limit });
|
||||
return await getTimetableAsync(
|
||||
input.baseUrl,
|
||||
{ stationId: input.stationId, limit: input.limit },
|
||||
input.pinnedAddresses,
|
||||
);
|
||||
},
|
||||
cacheTtlMs: 60_000,
|
||||
});
|
||||
@@ -142,6 +146,7 @@ const searchStationsAsync = async (
|
||||
const getTimetableAsync = async (
|
||||
baseUrl: string,
|
||||
options: { stationId: string; limit: number },
|
||||
pinnedAddresses?: TimetableResolvedAddress[],
|
||||
): Promise<Timetable> => {
|
||||
const now = new Date();
|
||||
const date = new Intl.DateTimeFormat("en-US", {
|
||||
@@ -156,48 +161,53 @@ const getTimetableAsync = async (
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
}).format(now);
|
||||
const response = await fetchWithTrustedCertificatesAsync(
|
||||
buildUrl(baseUrl, "/timetable/api/stationboard.json", {
|
||||
stop: options.stationId,
|
||||
limit: options.limit,
|
||||
show_delays: 1,
|
||||
show_tracks: 1,
|
||||
date,
|
||||
time,
|
||||
}),
|
||||
timetableFetchOptions,
|
||||
);
|
||||
if (!response.ok) throw new ResponseError(response);
|
||||
const dispatcher = pinnedAddresses ? await createPinnedTimetableDispatcherAsync(baseUrl, pinnedAddresses) : undefined;
|
||||
try {
|
||||
const response = await fetchWithTrustedCertificatesAsync(
|
||||
buildUrl(baseUrl, "/timetable/api/stationboard.json", {
|
||||
stop: options.stationId,
|
||||
limit: options.limit,
|
||||
show_delays: 1,
|
||||
show_tracks: 1,
|
||||
date,
|
||||
time,
|
||||
}),
|
||||
{ ...timetableFetchOptions, dispatcher },
|
||||
);
|
||||
if (!response.ok) throw new ResponseError(response);
|
||||
|
||||
const body = await readBoundedTimetableJsonAsync(response);
|
||||
const envelope = timetableEnvelopeSchema.parse(body);
|
||||
const data = await timetableSchema.parseAsync({
|
||||
connections: envelope.connections.slice(0, Math.min(options.limit, MAX_TIMETABLE_ENTRIES)),
|
||||
});
|
||||
return {
|
||||
stationId: options.stationId,
|
||||
timestamp: now,
|
||||
entries: data.connections.map((connection) => {
|
||||
const color = connection.color.split("~")[0];
|
||||
return {
|
||||
timestamp: connection.time,
|
||||
line: connection.line
|
||||
? {
|
||||
name: connection.line,
|
||||
color: color && color.length >= 1 ? `#${color}` : null,
|
||||
}
|
||||
: null,
|
||||
location: connection.terminal.name,
|
||||
delay: connection.dep_delay,
|
||||
platform: connection.track
|
||||
? {
|
||||
name: connection.track.replace("!", ""),
|
||||
hasChanged: connection.track.includes("!"),
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}),
|
||||
};
|
||||
const body = await readBoundedTimetableJsonAsync(response);
|
||||
const envelope = timetableEnvelopeSchema.parse(body);
|
||||
const data = await timetableSchema.parseAsync({
|
||||
connections: envelope.connections.slice(0, Math.min(options.limit, MAX_TIMETABLE_ENTRIES)),
|
||||
});
|
||||
return {
|
||||
stationId: options.stationId,
|
||||
timestamp: now,
|
||||
entries: data.connections.map((connection) => {
|
||||
const color = connection.color.split("~")[0];
|
||||
return {
|
||||
timestamp: connection.time,
|
||||
line: connection.line
|
||||
? {
|
||||
name: connection.line,
|
||||
color: color && color.length >= 1 ? `#${color}` : null,
|
||||
}
|
||||
: null,
|
||||
location: connection.terminal.name,
|
||||
delay: connection.dep_delay,
|
||||
platform: connection.track
|
||||
? {
|
||||
name: connection.track.replace("!", ""),
|
||||
hasChanged: connection.track.includes("!"),
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}),
|
||||
};
|
||||
} finally {
|
||||
await dispatcher?.close();
|
||||
}
|
||||
};
|
||||
|
||||
const timetableSchema = z.object({
|
||||
|
||||
Reference in new issue
Block a user