* fix(app): open notifications on the right host Agent notifications previously omitted workspace ownership, so a cold target host was treated as missing and fell back to its empty home route. Carry the authoritative workspace and keep older notifications on a target-host resolver until lookup is conclusive. * fix(app): separate notification and agent URL routing Notifications use their authoritative workspace target directly. Stable agent URLs remain server-and-agent targets whose workspace is resolved by the agent route. * fix(protocol): preserve notification workspace targets Both attention message variants retain workspaceId through outbound validation so notification clicks receive the authoritative route target. * test(app): use workspace-scoped notification target * test(server): give notification agents workspace targets * test(app): target notification workspace in navigation e2e
105 lines
2.9 KiB
TypeScript
105 lines
2.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { buildNotificationRoute, resolveNotificationTarget } from "./notification-routing";
|
|
|
|
describe("resolveNotificationTarget", () => {
|
|
it("extracts non-empty server and agent ids", () => {
|
|
expect(
|
|
resolveNotificationTarget({
|
|
serverId: " server-123 ",
|
|
agentId: " agent-456 ",
|
|
}),
|
|
).toEqual({
|
|
serverId: "server-123",
|
|
agentId: "agent-456",
|
|
workspaceId: null,
|
|
terminalId: null,
|
|
});
|
|
});
|
|
|
|
it("returns null for missing/empty ids", () => {
|
|
expect(resolveNotificationTarget({ serverId: "", agentId: " " })).toEqual({
|
|
serverId: null,
|
|
agentId: null,
|
|
workspaceId: null,
|
|
terminalId: null,
|
|
});
|
|
expect(resolveNotificationTarget(undefined)).toEqual({
|
|
serverId: null,
|
|
agentId: null,
|
|
workspaceId: null,
|
|
terminalId: null,
|
|
});
|
|
});
|
|
|
|
it("does not treat cwd as a workspace id alias", () => {
|
|
expect(
|
|
resolveNotificationTarget({
|
|
serverId: "srv-1",
|
|
agentId: "agent-1",
|
|
cwd: "/tmp/repo",
|
|
}),
|
|
).toEqual({
|
|
serverId: "srv-1",
|
|
agentId: "agent-1",
|
|
workspaceId: null,
|
|
terminalId: null,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("buildNotificationRoute", () => {
|
|
it("routes to the agent path when workspace id is present", () => {
|
|
expect(
|
|
buildNotificationRoute({
|
|
serverId: "srv-1",
|
|
agentId: "agent-1",
|
|
workspaceId: "ws-main",
|
|
}),
|
|
).toBe("/h/srv-1/workspace/ws-main?open=agent%3Aagent-1");
|
|
});
|
|
|
|
it("does not treat an incomplete notification as an agent URL", () => {
|
|
expect(buildNotificationRoute({ serverId: "srv-1", agentId: "agent-1" })).toBe("/h/srv-1");
|
|
});
|
|
|
|
it("routes to the workspace terminal tab when workspace and terminal ids are present", () => {
|
|
expect(
|
|
buildNotificationRoute({
|
|
serverId: "srv-1",
|
|
workspaceId: "ws-main",
|
|
terminalId: "term-1",
|
|
}),
|
|
).toBe("/h/srv-1/workspace/ws-main?open=terminal%3Aterm-1");
|
|
});
|
|
|
|
it("falls back to host root for a terminal without a workspace id", () => {
|
|
expect(
|
|
buildNotificationRoute({
|
|
serverId: "srv-1",
|
|
terminalId: "term-1",
|
|
cwd: "/tmp/repo",
|
|
}),
|
|
).toBe("/h/srv-1");
|
|
});
|
|
|
|
it("falls back to host root when only serverId is present", () => {
|
|
expect(buildNotificationRoute({ serverId: "srv-only" })).toBe("/h/srv-only");
|
|
});
|
|
|
|
it("falls back to root when no server id is present", () => {
|
|
expect(buildNotificationRoute({ agentId: "agent-legacy" })).toBe("/");
|
|
expect(buildNotificationRoute(undefined)).toBe("/");
|
|
});
|
|
|
|
it("encodes path segments", () => {
|
|
expect(
|
|
buildNotificationRoute({
|
|
serverId: "srv/with/slash",
|
|
workspaceId: "workspace-1",
|
|
agentId: "agent with space",
|
|
}),
|
|
).toBe("/h/srv%2Fwith%2Fslash/workspace/workspace-1?open=agent%3Aagent%20with%20space");
|
|
});
|
|
});
|