import type { ChatMessage, StorageNamespace } from "@dispatch/kernel"; import { beforeEach, describe, expect, it } from "vitest"; import { createConversationStore, isValidWorkspaceSlug } from "./store.js"; function createMemoryStorage(): StorageNamespace { const data = new Map(); return { get: async (key) => data.get(key) ?? null, set: async (key, value) => { data.set(key, value); }, delete: async (key) => { data.delete(key); }, has: async (key) => data.has(key), keys: async (prefix) => { const all = [...data.keys()]; if (!prefix) return all; return all.filter((k) => k.startsWith(prefix)); }, }; } describe("WorkspaceStore", () => { let storage: StorageNamespace; let clock: number; beforeEach(() => { storage = createMemoryStorage(); clock = 1000; }); function makeStore(serverDefaultCwd?: string) { return createConversationStore(storage, undefined, () => clock, serverDefaultCwd); } function userMessage(text: string): ChatMessage { return { role: "user", chunks: [{ type: "text", text }] }; } it("ensureWorkspace creates with defaults", async () => { const store = makeStore(); clock = 1000; const ws = await store.ensureWorkspace("my-work"); expect(ws).toEqual({ id: "my-work", title: "my-work", defaultCwd: null, defaultComputerId: null, createdAt: 1000, lastActivityAt: 1000, }); }); it("ensureWorkspace returns existing as-is", async () => { const store = makeStore(); clock = 1000; await store.ensureWorkspace("my-work"); clock = 2000; const ws = await store.ensureWorkspace("my-work", { title: "New Title", defaultCwd: "/ignored", }); expect(ws).toEqual({ id: "my-work", title: "my-work", defaultCwd: null, defaultComputerId: null, createdAt: 1000, lastActivityAt: 1000, }); }); it("ensureWorkspace with custom title/defaultCwd", async () => { const store = makeStore(); clock = 3000; const ws = await store.ensureWorkspace("my-work", { title: "Custom", defaultCwd: "/projects/dispatch", }); expect(ws).toEqual({ id: "my-work", title: "Custom", defaultCwd: "/projects/dispatch", defaultComputerId: null, createdAt: 3000, lastActivityAt: 3000, }); }); it("getWorkspace synthesizes default", async () => { const store = makeStore(); const ws = await store.getWorkspace("default"); expect(ws).toEqual({ id: "default", title: "default", defaultCwd: null, defaultComputerId: null, createdAt: 0, lastActivityAt: 0, }); }); it("getWorkspace returns null for unknown", async () => { const store = makeStore(); expect(await store.getWorkspace("unknown")).toBeNull(); }); it("setWorkspaceTitle renames", async () => { const store = makeStore(); clock = 1000; await store.ensureWorkspace("my-work"); clock = 2000; const ws = await store.setWorkspaceTitle("my-work", "Renamed"); expect(ws).toEqual({ id: "my-work", title: "Renamed", defaultCwd: null, defaultComputerId: null, createdAt: 1000, lastActivityAt: 1000, }); }); it("setWorkspaceDefaultCwd sets and clears", async () => { const store = makeStore(); clock = 1000; await store.ensureWorkspace("my-work"); clock = 2000; const setWs = await store.setWorkspaceDefaultCwd("my-work", "/some/path"); expect(setWs.defaultCwd).toBe("/some/path"); expect(setWs.lastActivityAt).toBe(1000); // does not bump on defaultCwd change const cleared = await store.setWorkspaceDefaultCwd("my-work", null); expect(cleared.defaultCwd).toBeNull(); }); it("deleteWorkspace closes conversations", async () => { const store = makeStore(); clock = 1000; await store.ensureWorkspace("work-a"); await store.setWorkspaceId("conv1", "work-a"); await store.setWorkspaceId("conv2", "work-a"); await store.setWorkspaceId("conv3", "default"); clock = 2000; await store.append("conv1", [userMessage("hi 1")]); await store.append("conv2", [userMessage("hi 2")]); await store.append("conv3", [userMessage("hi 3")]); const result = await store.deleteWorkspace("work-a"); expect(result.closedCount).toBe(2); const meta1 = await store.getConversationMeta("conv1"); expect(meta1?.status).toBe("closed"); expect(meta1?.workspaceId).toBe("default"); const meta2 = await store.getConversationMeta("conv2"); expect(meta2?.status).toBe("closed"); expect(meta2?.workspaceId).toBe("default"); const meta3 = await store.getConversationMeta("conv3"); expect(meta3?.status).toBe("idle"); expect(meta3?.workspaceId).toBe("default"); expect(await store.getWorkspace("work-a")).toBeNull(); }); it("deleteWorkspace throws for default", async () => { const store = makeStore(); await expect(store.deleteWorkspace("default")).rejects.toThrow(); }); it("listWorkspaces sorted by lastActivityAt desc", async () => { const store = makeStore(); clock = 1000; await store.ensureWorkspace("alpha"); clock = 2000; await store.ensureWorkspace("beta"); clock = 3000; await store.ensureWorkspace("gamma"); const list = await store.listWorkspaces(); expect(list.map((w) => w.id)).toEqual(["gamma", "beta", "alpha", "default"]); }); it("listWorkspaces includes conversationCount", async () => { const store = makeStore(); clock = 1000; await store.ensureWorkspace("work-a"); await store.ensureWorkspace("work-b"); await store.setWorkspaceId("a1", "work-a"); await store.setWorkspaceId("a2", "work-a"); await store.setWorkspaceId("b1", "work-b"); await store.append("lonely", [userMessage("hi")]); // defaults to "default" const list = await store.listWorkspaces(); const counts = Object.fromEntries(list.map((w) => [w.id, w.conversationCount])); expect(counts["work-a"]).toBe(2); expect(counts["work-b"]).toBe(1); expect(counts.default).toBe(1); }); it("listWorkspaces always includes default", async () => { const store = makeStore(); clock = 1000; await store.ensureWorkspace("only"); // No append or explicit default creation — default is synthesized. const list = await store.listWorkspaces(); const ids = list.map((w) => w.id); expect(ids).toContain("default"); const defaultWs = list.find((w) => w.id === "default"); expect(defaultWs).toEqual({ id: "default", title: "default", defaultCwd: null, defaultComputerId: null, createdAt: 0, lastActivityAt: 0, conversationCount: 0, }); }); it("getWorkspaceId returns default for legacy", async () => { const store = makeStore(); await store.append("conv1", [userMessage("hi")]); expect(await store.getWorkspaceId("conv1")).toBe("default"); expect(await store.getWorkspaceId("never-seen")).toBe("default"); }); it("setWorkspaceId persists and reads back", async () => { const store = makeStore(); clock = 1000; await store.setWorkspaceId("conv1", "my-work"); expect(await store.getWorkspaceId("conv1")).toBe("my-work"); const meta = await store.getConversationMeta("conv1"); expect(meta?.workspaceId).toBe("my-work"); expect(meta?.status).toBe("idle"); }); it("getEffectiveCwd: absolute conversation cwd overrides workspace defaultCwd", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work", { defaultCwd: "/workspace/default" }); await store.setWorkspaceId("conv1", "my-work"); await store.setCwd("conv1", "/explicit/path"); expect(await store.getEffectiveCwd("conv1")).toBe("/explicit/path"); }); it("getEffectiveCwd: workspace defaultCwd used when conversation cwd is unset (bug fix)", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work", { defaultCwd: "/workspace/default" }); await store.setWorkspaceId("conv1", "my-work"); expect(await store.getEffectiveCwd("conv1")).toBe("/workspace/default"); }); it("getEffectiveCwd: serverDefaultCwd fallback when both conversation and workspace cwd are null", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work"); await store.setWorkspaceId("conv1", "my-work"); expect(await store.getEffectiveCwd("conv1")).toBe("/server/default"); }); it("getEffectiveCwd: relative conversation cwd resolved against workspace defaultCwd", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work", { defaultCwd: "/workspace/root" }); await store.setWorkspaceId("conv1", "my-work"); await store.setCwd("conv1", "subdir"); expect(await store.getEffectiveCwd("conv1")).toBe("/workspace/root/subdir"); }); it("getEffectiveCwd: relative conversation cwd resolved against serverDefaultCwd when workspace defaultCwd is null", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work"); await store.setWorkspaceId("conv1", "my-work"); await store.setCwd("conv1", "subdir"); expect(await store.getEffectiveCwd("conv1")).toBe("/server/default/subdir"); }); it("getEffectiveCwd: relative cwd with nested segments resolved against workspace defaultCwd", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work", { defaultCwd: "/workspace/root" }); await store.setWorkspaceId("conv1", "my-work"); await store.setCwd("conv1", "a/b/c"); expect(await store.getEffectiveCwd("conv1")).toBe("/workspace/root/a/b/c"); }); it("getEffectiveCwd: relative cwd with .. segments normalizes via path.resolve", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work", { defaultCwd: "/workspace/root/sub" }); await store.setWorkspaceId("conv1", "my-work"); await store.setCwd("conv1", "../sibling"); expect(await store.getEffectiveCwd("conv1")).toBe("/workspace/root/sibling"); }); it("getEffectiveCwd: default workspace (no defaultCwd) falls through to serverDefaultCwd", async () => { const store = makeStore("/server/default"); // No explicit workspace assignment — defaults to "default" workspace // which has defaultCwd null. expect(await store.getEffectiveCwd("conv1")).toBe("/server/default"); }); // --- overrideCwd (per-turn cwd override) --- it("getEffectiveCwd: overrideCwd absolute (starts with /) returned as-is, overriding workspace defaultCwd", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work", { defaultCwd: "/workspace/default" }); await store.setWorkspaceId("conv1", "my-work"); // An absolute override wins outright, even over a workspace defaultCwd. expect(await store.getEffectiveCwd("conv1", "/override/abs")).toBe("/override/abs"); }); it("getEffectiveCwd: overrideCwd relative resolved against workspace defaultCwd", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work", { defaultCwd: "/workspace/root" }); await store.setWorkspaceId("conv1", "my-work"); expect(await store.getEffectiveCwd("conv1", "subdir")).toBe("/workspace/root/subdir"); }); it("getEffectiveCwd: overrideCwd relative resolved against serverDefaultCwd when workspace defaultCwd is null", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work"); await store.setWorkspaceId("conv1", "my-work"); expect(await store.getEffectiveCwd("conv1", "subdir")).toBe("/server/default/subdir"); }); it("getEffectiveCwd: overrideCwd does NOT read the persisted getCwd (override wins over persisted)", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work", { defaultCwd: "/workspace/root" }); await store.setWorkspaceId("conv1", "my-work"); // Persist a cwd that differs from the override — the override must win. await store.setCwd("conv1", "/persisted/path"); expect(await store.getEffectiveCwd("conv1", "override-rel")).toBe( "/workspace/root/override-rel", ); }); it("getEffectiveCwd: overrideCwd omitted behaves as today (uses persisted cwd)", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work", { defaultCwd: "/workspace/root" }); await store.setWorkspaceId("conv1", "my-work"); await store.setCwd("conv1", "persisted-rel"); // No second arg — persisted cwd is used. expect(await store.getEffectiveCwd("conv1")).toBe("/workspace/root/persisted-rel"); }); it("clearCwd → getEffectiveCwd falls through to workspace defaultCwd (un-shadows it)", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work", { defaultCwd: "/workspace/default" }); await store.setWorkspaceId("conv1", "my-work"); await store.setCwd("conv1", "/explicit/path"); // Before clear: the conversation cwd shadows the workspace defaultCwd. expect(await store.getEffectiveCwd("conv1")).toBe("/explicit/path"); // After clear: the workspace defaultCwd is used (fall-through). await store.clearCwd("conv1"); expect(await store.getEffectiveCwd("conv1")).toBe("/workspace/default"); }); it("getEffectiveCwd: an empty-string cwd does NOT fall through (proving clear ≠ setCwd(''))", async () => { const store = makeStore("/server/default"); await store.ensureWorkspace("my-work", { defaultCwd: "/workspace/default" }); await store.setWorkspaceId("conv1", "my-work"); // An empty string is a non-null explicit cwd — it is resolved (not // treated as absent), so it does NOT fall through to the workspace // defaultCwd. This is the gap clearCwd fixes. await store.setCwd("conv1", ""); expect(await store.getCwd("conv1")).toBe(""); // path.resolve("/workspace/default", "") === "/workspace/default" — // but this is a RELATIVE cwd resolution, not a fall-through. The point // is that getCwd returns "" (not null), so the relative branch runs. // With a clearCwd, getCwd returns null and the fall-through branch runs. await store.clearCwd("conv1"); expect(await store.getCwd("conv1")).toBeNull(); expect(await store.getEffectiveCwd("conv1")).toBe("/workspace/default"); }); it("listConversations filtered by workspaceId", async () => { const store = makeStore(); await store.ensureWorkspace("work-a"); await store.ensureWorkspace("work-b"); await store.append("a1", [userMessage("a1")]); await store.append("a2", [userMessage("a2")]); await store.append("b1", [userMessage("b1")]); await store.setWorkspaceId("a1", "work-a"); await store.setWorkspaceId("a2", "work-a"); await store.setWorkspaceId("b1", "work-b"); const aConvs = await store.listConversations({ workspaceId: "work-a" }); expect(aConvs.map((c) => c.id).sort()).toEqual(["a1", "a2"]); const bConvs = await store.listConversations({ workspaceId: "work-b" }); expect(bConvs.map((c) => c.id)).toEqual(["b1"]); }); it("append updates workspace lastActivityAt", async () => { const store = makeStore(); clock = 1000; await store.ensureWorkspace("my-work"); clock = 2000; await store.setWorkspaceId("conv1", "my-work"); clock = 3000; await store.append("conv1", [userMessage("hi")]); const ws = await store.getWorkspace("my-work"); expect(ws?.lastActivityAt).toBe(3000); }); it("forkHistory copies workspaceId", async () => { const store = makeStore(); await store.ensureWorkspace("my-work"); await store.setWorkspaceId("source", "my-work"); await store.append("source", [userMessage("hello")]); await store.forkHistory("source", "target"); const targetMeta = await store.getConversationMeta("target"); expect(targetMeta?.workspaceId).toBe("my-work"); }); it("replaceHistory preserves workspaceId", async () => { const store = makeStore(); await store.ensureWorkspace("my-work"); await store.setWorkspaceId("conv1", "my-work"); await store.append("conv1", [userMessage("original")]); await store.replaceHistory("conv1", [userMessage("replaced")]); const meta = await store.getConversationMeta("conv1"); expect(meta?.workspaceId).toBe("my-work"); }); }); describe("ComputerStore", () => { let storage: StorageNamespace; let clock: number; beforeEach(() => { storage = createMemoryStorage(); clock = 1000; }); function makeStore() { return createConversationStore(storage, undefined, () => clock); } // --- per-conversation computerId (mirror getCwd/setCwd/clearCwd) --- it("setComputerId/getComputerId round-trips an alias", async () => { const store = makeStore(); expect(await store.getComputerId("conv1")).toBeNull(); await store.setComputerId("conv1", "myserver"); expect(await store.getComputerId("conv1")).toBe("myserver"); }); it("setComputerId(null) clears (is idempotent local sentinel, like clearComputerId)", async () => { const store = makeStore(); await store.setComputerId("conv1", "myserver"); expect(await store.getComputerId("conv1")).toBe("myserver"); // null is the "local" sentinel: it clears the persisted key so it does // NOT linger to shadow the workspace defaultComputerId. await store.setComputerId("conv1", null); expect(await store.getComputerId("conv1")).toBeNull(); // idempotent — clearing an already-absent key is a no-op. await store.setComputerId("conv1", null); expect(await store.getComputerId("conv1")).toBeNull(); }); it("clearComputerId is idempotent and un-shadows the workspace default", async () => { const store = makeStore(); await store.ensureWorkspace("my-work", { defaultComputerId: "ws-host" }); await store.setWorkspaceId("conv1", "my-work"); await store.setComputerId("conv1", "per-conv-host"); expect(await store.getEffectiveComputer("conv1")).toBe("per-conv-host"); // After clear: the workspace defaultComputerId is used (fall-through). await store.clearComputerId("conv1"); expect(await store.getComputerId("conv1")).toBeNull(); expect(await store.getEffectiveComputer("conv1")).toBe("ws-host"); // idempotent — deleting an already-absent key is a no-op. await store.clearComputerId("conv1"); expect(await store.getComputerId("conv1")).toBeNull(); }); // --- setWorkspaceDefaultComputerId (mirror setWorkspaceDefaultCwd) --- it("setWorkspaceDefaultComputerId sets and clears", async () => { const store = makeStore(); clock = 1000; await store.ensureWorkspace("my-work"); clock = 2000; const setWs = await store.setWorkspaceDefaultComputerId("my-work", "remote-host"); expect(setWs.defaultComputerId).toBe("remote-host"); // does not bump lastActivityAt on defaultComputerId change (mirrors defaultCwd). expect(setWs.lastActivityAt).toBe(1000); const cleared = await store.setWorkspaceDefaultComputerId("my-work", null); expect(cleared.defaultComputerId).toBeNull(); }); it("setWorkspaceDefaultComputerId creates the workspace if missing", async () => { const store = makeStore(); clock = 5000; const ws = await store.setWorkspaceDefaultComputerId("brand-new", "remote-host"); expect(ws).toEqual({ id: "brand-new", title: "brand-new", defaultCwd: null, defaultComputerId: "remote-host", createdAt: 5000, lastActivityAt: 5000, }); }); it("setWorkspaceDefaultComputerId preserves defaultCwd on an existing workspace", async () => { const store = makeStore(); clock = 1000; await store.ensureWorkspace("my-work", { defaultCwd: "/workspace/root" }); clock = 2000; const ws = await store.setWorkspaceDefaultComputerId("my-work", "remote-host"); expect(ws.defaultCwd).toBe("/workspace/root"); expect(ws.defaultComputerId).toBe("remote-host"); }); it("the synthesized 'default' workspace still returns defaultComputerId: null (local)", async () => { const store = makeStore(); const ws = await store.getWorkspace("default"); expect(ws).toEqual({ id: "default", title: "default", defaultCwd: null, defaultComputerId: null, createdAt: 0, lastActivityAt: 0, }); // And it surfaces null in listWorkspaces too. const list = await store.listWorkspaces(); const defaultWs = list.find((w) => w.id === "default"); expect(defaultWs?.defaultComputerId).toBeNull(); }); // --- getEffectiveComputer resolution ladder (mirror getEffectiveCwd) --- it("getEffectiveComputer: per-conversation computerId overrides workspace defaultComputerId", async () => { const store = makeStore(); await store.ensureWorkspace("my-work", { defaultComputerId: "ws-host" }); await store.setWorkspaceId("conv1", "my-work"); await store.setComputerId("conv1", "per-conv-host"); expect(await store.getEffectiveComputer("conv1")).toBe("per-conv-host"); }); it("getEffectiveComputer: workspace defaultComputerId used when conversation computerId is unset", async () => { const store = makeStore(); await store.ensureWorkspace("my-work", { defaultComputerId: "ws-host" }); await store.setWorkspaceId("conv1", "my-work"); expect(await store.getEffectiveComputer("conv1")).toBe("ws-host"); }); it("getEffectiveComputer: null (LOCAL) when both conversation and workspace computerId are unset", async () => { const store = makeStore(); await store.ensureWorkspace("my-work"); await store.setWorkspaceId("conv1", "my-work"); expect(await store.getEffectiveComputer("conv1")).toBeNull(); }); it("getEffectiveComputer: default workspace (no defaultComputerId) falls through to null (local)", async () => { const store = makeStore(); // No explicit workspace assignment — defaults to "default" workspace // which has defaultComputerId null. expect(await store.getEffectiveComputer("conv1")).toBeNull(); }); it("getEffectiveComputer: clearComputerId falls through to workspace defaultComputerId (un-shadows it)", async () => { const store = makeStore(); await store.ensureWorkspace("my-work", { defaultComputerId: "ws-host" }); await store.setWorkspaceId("conv1", "my-work"); await store.setComputerId("conv1", "per-conv-host"); // Before clear: the conversation computerId shadows the workspace default. expect(await store.getEffectiveComputer("conv1")).toBe("per-conv-host"); // After clear: the workspace defaultComputerId is used (fall-through). await store.clearComputerId("conv1"); expect(await store.getEffectiveComputer("conv1")).toBe("ws-host"); }); // --- overrideAlias (per-turn computer override, mirror overrideCwd) --- it("getEffectiveComputer: overrideAlias string wins outright, overriding workspace defaultComputerId", async () => { const store = makeStore(); await store.ensureWorkspace("my-work", { defaultComputerId: "ws-host" }); await store.setWorkspaceId("conv1", "my-work"); // A string override wins outright, even over a workspace defaultComputerId. expect(await store.getEffectiveComputer("conv1", "override-host")).toBe("override-host"); }); it("getEffectiveComputer: overrideAlias string wins over the persisted per-conversation computerId", async () => { const store = makeStore(); await store.ensureWorkspace("my-work", { defaultComputerId: "ws-host" }); await store.setWorkspaceId("conv1", "my-work"); await store.setComputerId("conv1", "persisted-host"); // The override must win over the persisted computerId. expect(await store.getEffectiveComputer("conv1", "override-host")).toBe("override-host"); }); it("getEffectiveComputer: overrideAlias null is explicitly local and does NOT fall through", async () => { const store = makeStore(); await store.ensureWorkspace("my-work", { defaultComputerId: "ws-host" }); await store.setWorkspaceId("conv1", "my-work"); await store.setComputerId("conv1", "persisted-host"); // An explicit null override = "local for this turn": it wins outright and // does NOT fall through to the persisted value or the workspace default. expect(await store.getEffectiveComputer("conv1", null)).toBeNull(); }); it("getEffectiveComputer: overrideAlias omitted behaves as today (uses persisted computerId)", async () => { const store = makeStore(); await store.ensureWorkspace("my-work", { defaultComputerId: "ws-host" }); await store.setWorkspaceId("conv1", "my-work"); await store.setComputerId("conv1", "persisted-host"); // No second arg — persisted computerId is used. expect(await store.getEffectiveComputer("conv1")).toBe("persisted-host"); }); // --- round-trip through persistence (parse/toWorkspace) --- it("a Workspace with defaultComputerId round-trips through parse/toWorkspace", async () => { const store = makeStore(); clock = 1000; // Create with a defaultComputerId via ensureWorkspace, then read it back // (exercises parseWorkspaceRow -> toWorkspace round-trip). const created = await store.ensureWorkspace("remote-work", { title: "Remote", defaultComputerId: "prod-server", }); expect(created.defaultComputerId).toBe("prod-server"); const roundTripped = await store.getWorkspace("remote-work"); expect(roundTripped).toEqual({ id: "remote-work", title: "Remote", defaultCwd: null, defaultComputerId: "prod-server", createdAt: 1000, lastActivityAt: 1000, }); }); it("a legacy WorkspaceRow without defaultComputerId reads back as null (local)", async () => { const store = makeStore(); // Simulate a legacy row persisted before defaultComputerId existed: // write a raw WorkspaceRow JSON lacking the field, then read it back. await storage.set( "workspace:legacy", JSON.stringify({ title: "legacy", defaultCwd: "/legacy/cwd", createdAt: 100, lastActivityAt: 200, }), ); const ws = await store.getWorkspace("legacy"); expect(ws).toEqual({ id: "legacy", title: "legacy", defaultCwd: "/legacy/cwd", defaultComputerId: null, createdAt: 100, lastActivityAt: 200, }); }); }); describe("isValidWorkspaceSlug", () => { it("accepts valid slugs", () => { expect(isValidWorkspaceSlug("my-work")).toBe(true); expect(isValidWorkspaceSlug("default")).toBe(true); expect(isValidWorkspaceSlug("a1b2")).toBe(true); }); it("rejects invalid slugs", () => { expect(isValidWorkspaceSlug("My-Work")).toBe(false); expect(isValidWorkspaceSlug("-leading")).toBe(false); expect(isValidWorkspaceSlug("trailing-")).toBe(false); expect(isValidWorkspaceSlug("")).toBe(false); expect(isValidWorkspaceSlug("a".repeat(41))).toBe(false); expect(isValidWorkspaceSlug("has space")).toBe(false); }); });