{/* Quick Filters Bar */}
-
+
{/* Active Filters Display */}
{
+ root.render(
+
+ {node}
+
+ );
+ });
+
+ return {
+ container,
+ unmount: () => {
+ act(() => root.unmount());
+ container.remove();
+ },
+ };
+}
+
+async function actClick(el: Element | null) {
+ if (!el) throw new Error("element not found");
+ await act(async () => {
+ el.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+ });
+}
+
+/**
+ * The quick filters bar and the date range picker both render a "Today" button.
+ * They are told apart by the icon: quick filter buttons carry a lucide icon,
+ * picker quick-period buttons are text-only.
+ */
+function findButton(
+ container: Element,
+ text: string,
+ options?: { withIcon?: boolean }
+): HTMLButtonElement | undefined {
+ return Array.from(container.querySelectorAll("button")).find((button) => {
+ if ((button.textContent || "").trim() !== text) return false;
+ if (options?.withIcon === undefined) return true;
+ const hasIcon = button.querySelector("svg") !== null;
+ return hasIcon === options.withIcon;
+ });
+}
+
+function isPressed(button: HTMLButtonElement | undefined): boolean {
+ return button?.getAttribute("aria-pressed") === "true";
+}
+
+function getTimeInputs(container: Element): HTMLInputElement[] {
+ return Array.from(container.querySelectorAll("input[type='time']"));
+}
+
+async function changeInputValue(input: HTMLInputElement, value: string) {
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
+ if (!setter) throw new Error("missing native input value setter");
+ await act(async () => {
+ setter.call(input, value);
+ input.dispatchEvent(new Event("input", { bubbles: true }));
+ });
+}
+
+function renderFilters() {
+ return renderWithIntl(
+ {}}
+ onReset={() => {}}
+ />
+ );
+}
+
+describe("UsageLogsFilters - quick filter linkage", () => {
+ test("quick bar Today lights up the picker Today and fills the date/time display", async () => {
+ const { container, unmount } = renderFilters();
+
+ const quickToday = findButton(container, "Today", { withIcon: true });
+ const pickerToday = findButton(container, "Today", { withIcon: false });
+ expect(quickToday).toBeDefined();
+ expect(pickerToday).toBeDefined();
+ expect(isPressed(quickToday)).toBe(false);
+ expect(isPressed(pickerToday)).toBe(false);
+
+ await actClick(quickToday ?? null);
+
+ expect(isPressed(findButton(container, "Today", { withIcon: true }))).toBe(true);
+ expect(isPressed(findButton(container, "Today", { withIcon: false }))).toBe(true);
+
+ const today = format(new Date(), "yyyy-MM-dd");
+ const rangeTrigger = findButton(container, today);
+ expect(rangeTrigger).toBeDefined();
+
+ const [startInput, endInput] = getTimeInputs(container);
+ expect(startInput?.value).toBe("00:00");
+ expect(endInput?.value).toBe("23:59:59");
+
+ unmount();
+ });
+
+ test("selecting Today in the date range picker lights up the quick bar Today", async () => {
+ const { container, unmount } = renderFilters();
+
+ await actClick(findButton(container, "Today", { withIcon: false }) ?? null);
+
+ expect(isPressed(findButton(container, "Today", { withIcon: false }))).toBe(true);
+ expect(isPressed(findButton(container, "Today", { withIcon: true }))).toBe(true);
+
+ const [startInput, endInput] = getTimeInputs(container);
+ expect(startInput?.value).toBe("00:00");
+ expect(endInput?.value).toBe("23:59:59");
+
+ unmount();
+ });
+
+ test("selecting Yesterday in the picker does not light up the quick bar Today", async () => {
+ const { container, unmount } = renderFilters();
+
+ await actClick(findButton(container, "Yesterday", { withIcon: false }) ?? null);
+
+ expect(isPressed(findButton(container, "Yesterday", { withIcon: false }))).toBe(true);
+ expect(isPressed(findButton(container, "Today", { withIcon: true }))).toBe(false);
+ expect(isPressed(findButton(container, "Today", { withIcon: false }))).toBe(false);
+
+ unmount();
+ });
+
+ test("clicking the active quick bar preset again clears the time range", async () => {
+ const { container, unmount } = renderFilters();
+
+ await actClick(findButton(container, "Today", { withIcon: true }) ?? null);
+ expect(isPressed(findButton(container, "Today", { withIcon: true }))).toBe(true);
+
+ await actClick(findButton(container, "Today", { withIcon: true }) ?? null);
+
+ expect(isPressed(findButton(container, "Today", { withIcon: true }))).toBe(false);
+ expect(isPressed(findButton(container, "Today", { withIcon: false }))).toBe(false);
+
+ const [startInput, endInput] = getTimeInputs(container);
+ expect(startInput?.value).toBe("");
+ expect(endInput?.value).toBe("");
+
+ unmount();
+ });
+
+ test("clicking the active picker period again clears the range and the quick bar highlight", async () => {
+ const { container, unmount } = renderFilters();
+
+ await actClick(findButton(container, "Today", { withIcon: false }) ?? null);
+ expect(isPressed(findButton(container, "Today", { withIcon: true }))).toBe(true);
+
+ await actClick(findButton(container, "Today", { withIcon: false }) ?? null);
+
+ expect(isPressed(findButton(container, "Today", { withIcon: false }))).toBe(false);
+ expect(isPressed(findButton(container, "Today", { withIcon: true }))).toBe(false);
+
+ const [startInput, endInput] = getTimeInputs(container);
+ expect(startInput?.value).toBe("");
+ expect(endInput?.value).toBe("");
+
+ unmount();
+ });
+
+ test("time presets and status presets can stay highlighted at the same time", async () => {
+ const { container, unmount } = renderFilters();
+
+ await actClick(findButton(container, "Today", { withIcon: true }) ?? null);
+ await actClick(findButton(container, "Errors Only") ?? null);
+
+ expect(isPressed(findButton(container, "Today", { withIcon: true }))).toBe(true);
+ expect(isPressed(findButton(container, "Errors Only"))).toBe(true);
+
+ await actClick(findButton(container, "With Retries") ?? null);
+
+ expect(isPressed(findButton(container, "Today", { withIcon: true }))).toBe(true);
+ expect(isPressed(findButton(container, "Errors Only"))).toBe(true);
+ expect(isPressed(findButton(container, "With Retries"))).toBe(true);
+
+ unmount();
+ });
+
+ test("changing the start clock breaks the exact Today preset highlight", async () => {
+ const { container, unmount } = renderFilters();
+
+ await actClick(findButton(container, "Today", { withIcon: true }) ?? null);
+ expect(isPressed(findButton(container, "Today", { withIcon: true }))).toBe(true);
+
+ const [startInput] = getTimeInputs(container);
+ if (!startInput) throw new Error("start time input not found");
+ await changeInputValue(startInput, "08:00:00");
+
+ expect(isPressed(findButton(container, "Today", { withIcon: true }))).toBe(false);
+
+ unmount();
+ });
+
+ test("quick bar This Week fills a Monday-Sunday range and toggles off", async () => {
+ const { container, unmount } = renderFilters();
+
+ await actClick(findButton(container, "This Week", { withIcon: true }) ?? null);
+
+ expect(isPressed(findButton(container, "This Week", { withIcon: true }))).toBe(true);
+
+ const [startInput, endInput] = getTimeInputs(container);
+ expect(startInput?.value).toBe("00:00");
+ expect(endInput?.value).toBe("23:59:59");
+
+ await actClick(findButton(container, "This Week", { withIcon: true }) ?? null);
+
+ expect(isPressed(findButton(container, "This Week", { withIcon: true }))).toBe(false);
+ expect(getTimeInputs(container)[0]?.value).toBe("");
+
+ unmount();
+ });
+});
diff --git a/tests/unit/dashboard-logs-time-range-utils.test.ts b/tests/unit/dashboard-logs-time-range-utils.test.ts
index f9e4738dc..cf247047e 100644
--- a/tests/unit/dashboard-logs-time-range-utils.test.ts
+++ b/tests/unit/dashboard-logs-time-range-utils.test.ts
@@ -2,8 +2,10 @@ import { format } from "date-fns";
import { describe, expect, test } from "vitest";
import {
dateStringWithClockToTimestamp,
+ detectQuickTimePreset,
formatClockFromTimestamp,
getQuickDateRange,
+ getQuickTimeRange,
inclusiveEndTimestampFromExclusive,
parseClockString,
type QuickPeriod,
@@ -115,4 +117,57 @@ describe("dashboard logs time range utils", () => {
expect([before, after]).toContain(range.startDate);
expect(range.endDate).toBe(range.startDate);
});
+
+ test("getQuickTimeRange returns today's full-day range with exclusive end", () => {
+ const now = new Date("2024-01-15T12:00:00Z");
+
+ expect(getQuickTimeRange("today", "UTC", now)).toEqual({
+ startTime: Date.UTC(2024, 0, 15, 0, 0, 0),
+ endTime: Date.UTC(2024, 0, 16, 0, 0, 0),
+ });
+ });
+
+ test("getQuickTimeRange resolves today in the given timezone", () => {
+ const now = new Date("2024-01-02T02:00:00Z");
+ const tz = "America/Los_Angeles"; // still 2024-01-01 (PST, UTC-8) there
+
+ const range = getQuickTimeRange("today", tz, now);
+ expect(range).toEqual({
+ startTime: Date.UTC(2024, 0, 1, 8, 0, 0),
+ endTime: Date.UTC(2024, 0, 2, 8, 0, 0),
+ });
+ });
+
+ test("getQuickTimeRange returns this-week as Monday through next Monday (exclusive)", () => {
+ const now = new Date("2024-01-17T12:00:00Z"); // Wednesday
+
+ expect(getQuickTimeRange("this-week", "UTC", now)).toEqual({
+ startTime: Date.UTC(2024, 0, 15, 0, 0, 0), // Monday 00:00:00
+ endTime: Date.UTC(2024, 0, 22, 0, 0, 0), // next Monday 00:00:00
+ });
+ });
+
+ test("detectQuickTimePreset round-trips getQuickTimeRange", () => {
+ const now = new Date("2024-01-17T12:00:00Z");
+
+ for (const preset of ["today", "this-week"] as const) {
+ const range = getQuickTimeRange(preset, "UTC", now);
+ expect(range).not.toBeNull();
+ expect(detectQuickTimePreset(range?.startTime, range?.endTime, "UTC", now)).toBe(preset);
+ }
+ });
+
+ test("detectQuickTimePreset returns null for custom or incomplete ranges", () => {
+ const now = new Date("2024-01-17T12:00:00Z");
+ const today = getQuickTimeRange("today", "UTC", now);
+ expect(today).not.toBeNull();
+ if (!today) return;
+
+ // A shifted start clock breaks the exact full-day preset
+ expect(
+ detectQuickTimePreset(today.startTime + 3_600_000, today.endTime, "UTC", now)
+ ).toBeNull();
+ expect(detectQuickTimePreset(undefined, today.endTime, "UTC", now)).toBeNull();
+ expect(detectQuickTimePreset(today.startTime, undefined, "UTC", now)).toBeNull();
+ });
});
From 8baef8be7f0eff9674e724e1d59e0bb0df084941 Mon Sep 17 00:00:00 2001
From: Ding <44717411+ding113@users.noreply.github.com>
Date: Mon, 3 Aug 2026 15:46:11 +0800
Subject: [PATCH 4/4] fix(message): restore indexed reserved session request
lookup (#1391)
* fix(message): restore canonical expression-index lookup for reserved identities
The canonical session lookup for reserved identities had drifted away
from the messageSessionIdentity expression index, causing queries to
miss the optimised index path. The condition now always anchors on
the expression-index column while still allowing owner-scoped
lookups to match legacy null-identity rows via the session_id
fallback.
Unscoped reserved lookups remain narrow and do not pick up
unrelated null-identity rows, preserving reserved identity
isolation.
* test(repository): assert coalesce in owner-scoped legacy identity fallback
Extend the reserved identity SQL contract test to verify that
the generated where clause uses coalesce when resolving
owner-scoped session requests for legacy clients.
* test(sessions): strengthen reserved identity query and route coverage
Add encoded-identity regression cases to the v1 session requests route
test, verifying that URL-encoded session identities (pfx: and sid:
prefixed) resolve correctly through the endpoint.
Rewrite the repository-level session request query tests to compile
the generated SQL via PgDialect instead of string matching, and
parameterize them across both pfx: and sid: identity prefixes. The
assertions now verify the exact coalesce expression, identity guards,
and parameter bindings for both owner-scoped legacy fallback and
unscoped canonical lookups.
* style(message): normalize comment punctuation to half-width comma
---
src/repository/message.ts | 13 ++--
tests/api/v1/sessions/sessions.test.ts | 10 +++
.../message-session-request-query.test.ts | 74 ++++++++++++-------
3 files changed, 66 insertions(+), 31 deletions(-)
diff --git a/src/repository/message.ts b/src/repository/message.ts
index 605614a31..60901edad 100644
--- a/src/repository/message.ts
+++ b/src/repository/message.ts
@@ -77,12 +77,13 @@ function messageSessionLookup(identityOrPhysicalId: string, ownerUserId?: number
function messageCanonicalSessionLookup(identity: string, ownerUserId?: number) {
const canonicalCondition = isReservedSessionIdentity(identity)
- ? ownerUserId !== undefined
- ? or(
- eq(messageRequest.sessionIdentity, identity),
- and(isNull(messageRequest.sessionIdentity), eq(messageRequest.sessionId, identity))
- )
- : eq(messageRequest.sessionIdentity, identity)
+ ? and(
+ // 保留 expression index 入口, 同时避免 reserved identity 混入同名物理 Session.
+ eq(messageSessionIdentity, identity),
+ ownerUserId !== undefined
+ ? or(eq(messageRequest.sessionIdentity, identity), isNull(messageRequest.sessionIdentity))
+ : eq(messageRequest.sessionIdentity, identity)
+ )
: eq(messageSessionIdentity, identity);
return and(
diff --git a/tests/api/v1/sessions/sessions.test.ts b/tests/api/v1/sessions/sessions.test.ts
index 2b89cac21..c1e3cff3c 100644
--- a/tests/api/v1/sessions/sessions.test.ts
+++ b/tests/api/v1/sessions/sessions.test.ts
@@ -137,6 +137,16 @@ describe("v1 session endpoints", () => {
expect(requests.response.status).toBe(200);
expect(getSessionRequestsMock).toHaveBeenCalledWith("s1", 2, 5, "desc");
+ for (const identity of ["pfx:scope:fingerprint", "sid:canonical-session"]) {
+ const encodedRequests = await callV1Route({
+ method: "GET",
+ pathname: `/api/v1/sessions/${encodeURIComponent(identity)}/requests?page=1&pageSize=20&order=desc`,
+ headers,
+ });
+ expect(encodedRequests.response.status).toBe(200);
+ expect(getSessionRequestsMock).toHaveBeenLastCalledWith(identity, 1, 20, "desc");
+ }
+
await callV1Route({
method: "GET",
pathname: "/api/v1/sessions/s1/requests",
diff --git a/tests/unit/repository/message-session-request-query.test.ts b/tests/unit/repository/message-session-request-query.test.ts
index c05a81e5c..530280233 100644
--- a/tests/unit/repository/message-session-request-query.test.ts
+++ b/tests/unit/repository/message-session-request-query.test.ts
@@ -1,4 +1,5 @@
import { beforeEach, describe, expect, test, vi } from "vitest";
+import { PgDialect } from "drizzle-orm/pg-core";
import { messageRequest } from "@/drizzle/schema";
import { keys as keysTable } from "@/drizzle/schema";
import {
@@ -50,6 +51,12 @@ type RequestRow = Pick<
const firstCreatedAt = new Date("2026-05-04T10:00:00.000Z");
const secondCreatedAt = new Date("2026-05-04T10:01:00.000Z");
+const dialect = new PgDialect();
+
+function compileWhere(values: readonly unknown[]) {
+ const query = dialect.sqlToQuery(values.at(0) as never);
+ return { sql: query.sql.toLowerCase(), params: query.params };
+}
describe("message repository session request queries", () => {
beforeEach(() => {
@@ -237,33 +244,50 @@ describe("message repository session request queries", () => {
expect(rowsWhere.match(/shared-session/g)).toHaveLength(1);
});
- test("includes a legacy null-identity physical fallback for owner-scoped reserved identities", async () => {
- const count = createDrizzleQuery([{ count: 1 }]);
- const rows = createDrizzleQuery([]);
- boundary.select.mockReturnValueOnce(count).mockReturnValueOnce(rows);
-
- await findRequestsBySessionIdentity("pfx:legacy-client", { ownerUserId: 17 } as never);
-
- for (const where of [sqlText(count.trace.where), sqlText(rows.trace.where)]) {
- expect(where).toContain("user_id");
- expect(where).toContain("is null");
- expect(where).toContain("session_id");
- expect(where.match(/pfx:legacy-client/g)).toHaveLength(2);
+ test.each(["pfx:legacy-client", "sid:legacy-client"])(
+ "includes an owner-scoped legacy fallback without aliasing a non-null identity: %s",
+ async (identity) => {
+ const count = createDrizzleQuery([{ count: 1 }]);
+ const rows = createDrizzleQuery([]);
+ boundary.select.mockReturnValueOnce(count).mockReturnValueOnce(rows);
+
+ await findRequestsBySessionIdentity(identity, { ownerUserId: 17 } as never);
+
+ for (const where of [count.trace.where, rows.trace.where]) {
+ const compiled = compileWhere(where);
+ expect(compiled.sql).toContain(
+ 'coalesce("message_request"."session_identity", "message_request"."session_id") ='
+ );
+ expect(compiled.sql).toContain(
+ '("message_request"."session_identity" = $2 or "message_request"."session_identity" is null)'
+ );
+ expect(compiled.sql).not.toContain('or "message_request"."session_id" =');
+ expect(compiled.sql).toContain('"message_request"."user_id" = $3');
+ expect(compiled.params.slice(0, 3)).toEqual([identity, identity, 17]);
+ }
}
- });
-
- test("does not add the legacy physical fallback for unscoped reserved identities", async () => {
- const count = createDrizzleQuery([{ count: 1 }]);
- const rows = createDrizzleQuery([]);
- boundary.select.mockReturnValueOnce(count).mockReturnValueOnce(rows);
-
- await findRequestsBySessionIdentity("pfx:canonical", {} as never);
-
- for (const where of [sqlText(count.trace.where), sqlText(rows.trace.where)]) {
- expect(where).not.toContain("session_identity is null");
- expect(where.match(/pfx:canonical/g)).toHaveLength(1);
+ );
+
+ test.each(["pfx:canonical", "sid:canonical"])(
+ "uses the canonical expression index with an explicit unscoped identity guard: %s",
+ async (identity) => {
+ const count = createDrizzleQuery([{ count: 1 }]);
+ const rows = createDrizzleQuery([]);
+ boundary.select.mockReturnValueOnce(count).mockReturnValueOnce(rows);
+
+ await findRequestsBySessionIdentity(identity, {} as never);
+
+ for (const where of [count.trace.where, rows.trace.where]) {
+ const compiled = compileWhere(where);
+ expect(compiled.sql).toContain(
+ 'coalesce("message_request"."session_identity", "message_request"."session_id") = $1 and "message_request"."session_identity" = $2'
+ );
+ expect(compiled.sql).not.toContain('"message_request"."session_identity" is null');
+ expect(compiled.sql).not.toContain('or "message_request"."session_id" =');
+ expect(compiled.params.slice(0, 2)).toEqual([identity, identity]);
+ }
}
- });
+ );
test("does not treat a reserved canonical identity as a physical Session alias", async () => {
const locator = createDrizzleQuery([