feat: add project-aware agent workspace
Generic Container CI/CD / test-build-publish (push) Successful in 2m9s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 2m39s

This commit is contained in:
2026-10-22 18:52:18 +08:00
parent c6a0e74e72
commit 2d8506fea0
191 changed files with 12373 additions and 3556 deletions
@@ -1,11 +1,57 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import {
fetchWorkbenchProjectPresentation,
parsePlaceSearchConfiguration,
fetchWorkbenchProjects,
clearWorkbenchProjectCache,
findOwnedWorkbenchProject,
parseProjectMapExtent
} from "./project-client";
describe("workbench project client", () => {
it("caches complete authorized lists across repeated openings and refreshes explicitly or after expiry", async () => {
const clock = vi.spyOn(Date, "now").mockReturnValue(100);
const fetch = vi.fn(async () => Response.json({ items: [{ project_id: "supply", gs_workspace: "tjwater_next", status: "active" }], total: 1, limit: 1000, offset: 0 }));
vi.stubGlobal("fetch", fetch);
const provider = async () => "owner-token";
await Promise.all([fetchWorkbenchProjects(provider), fetchWorkbenchProjects(provider)]);
for (let i = 0; i < 10; i++) await fetchWorkbenchProjects(provider);
expect(fetch).toHaveBeenCalledTimes(1);
await fetchWorkbenchProjects(provider, undefined, true);
expect(fetch).toHaveBeenCalledTimes(2);
clock.mockReturnValue(300_101);
await fetchWorkbenchProjects(provider);
expect(fetch).toHaveBeenCalledTimes(3);
clearWorkbenchProjectCache(provider);
await fetchWorkbenchProjects(provider);
expect(fetch).toHaveBeenCalledTimes(4);
});
it("uses the explicitly selected project for each request without rediscovering the demo project", async () => {
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
const getAccessToken = vi.fn(async () => "token");
const project = { project_id: "fengyang", gs_workspace: "fengyang_v2", status: "active" };
const scoped = await findOwnedWorkbenchProject({ project, getAccessToken });
const other = await findOwnedWorkbenchProject({ project: { ...project, project_id: "demo" }, getAccessToken });
expect(scoped).toEqual({ project, accessToken: "token" });
expect(other.project.project_id).toBe("demo");
expect(fetchMock).not.toHaveBeenCalled();
});
it("loads all authorized project pages and excludes inactive projects", async () => {
const fetchMock = vi.fn()
.mockResolvedValueOnce(new Response(JSON.stringify({ items: [
{ project_id: "old", gs_workspace: "old", status: "inactive" },
{ project_id: "demo", gs_workspace: "tjwater_next", status: "active" }
], total: 3, limit: 1000, offset: 0 })))
.mockResolvedValueOnce(new Response(JSON.stringify({ items: [
{ project_id: "fengyang", gs_workspace: "fengyang_v2", status: "active" }
], total: 3, limit: 1000, offset: 2 })));
vi.stubGlobal("fetch", fetchMock);
expect((await fetchWorkbenchProjects()).map((project) => project.project_id)).toEqual(["demo", "fengyang"]);
expect(String(fetchMock.mock.calls[1][0])).toContain("offset=2");
});
afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
@@ -158,3 +204,16 @@ describe("workbench project client", () => {
});
});
});
describe("project place search configuration", () => {
it("reads configured regions and preserves unrelated map configuration", () => {
const config = { enabled: true, provider: "tianditu", admin_code: "341126", region: "凤阳县" };
expect(parsePlaceSearchConfiguration({ legend: "existing", place_search: config })).toEqual(config);
});
it.each([undefined, {}, { place_search: null }, { place_search: { enabled: false } },
{ place_search: { enabled: true, provider: "tianditu", admin_code: "invalid", region: "凤阳县" } }
])("disables absent or invalid configuration: %j", (config) => {
expect(parsePlaceSearchConfiguration(config)).toBeNull();
});
});