fix: validate ticket repository summaries

This commit is contained in:
2026-09-01 02:37:44 +09:00
parent 9013754a3a
commit 9de2afbfc6
3 changed files with 42 additions and 6 deletions
@@ -1,3 +1,4 @@
import type { ApiResult } from "$lib/workspace/api/http";
import type {
Diagnostic,
GitCommitSummary,
@@ -529,6 +530,22 @@ export function parseRepositoryListResponse(
};
}
export function parseRepositoryListApiResult(
result: ApiResult<unknown>,
): ApiResult<RepositoryListResponse> {
if (result.data === null) return { data: null, error: result.error };
try {
return { data: parseRepositoryListResponse(result.data), error: null };
} catch (cause) {
return {
data: null,
error: cause instanceof Error
? cause.message
: "invalid repository list response",
};
}
}
export function parseRepositoryDetailResponse(
value: unknown,
): RepositoryDetailResponse {
@@ -1,14 +1,12 @@
import { redirect } from "@sveltejs/kit";
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
import { parseRepositoryListApiResult } from "$lib/workspace/api/workspace-model";
import {
canonicalResourceReference,
resourceKey,
} from "$lib/workspace/resource-links";
import type { WorkspaceOrchestratorStatus } from "$lib/workspace/tickets/ticket-panel";
import type {
RepositoryListResponse,
TicketDetail,
} from "$lib/workspace/sidebar/types";
import type { TicketDetail } from "$lib/workspace/sidebar/types";
import type { PageLoad } from "./$types";
export const load = (async ({ fetch, params }) => {
@@ -17,9 +15,9 @@ export const load = (async ({ fetch, params }) => {
params.workspaceId,
`/tickets/${encodeURIComponent(reference)}`,
);
const [ticket, repositories, orchestrator] = await Promise.all([
const [ticket, repositoriesRaw, orchestrator] = await Promise.all([
loadJson<TicketDetail>(fetch, ticketPath),
loadJson<RepositoryListResponse>(
loadJson<unknown>(
fetch,
workspaceApiPath(params.workspaceId, "/repositories"),
),
@@ -42,6 +40,8 @@ export const load = (async ({ fetch, params }) => {
);
}
}
const repositories = parseRepositoryListApiResult(repositoriesRaw);
return {
workspaceId: params.workspaceId,
ticketId: ticket.data?.id ?? reference,
@@ -3,6 +3,7 @@ declare const Deno: {
};
import {
parseRepositoryListApiResult,
parseRepositoryListResponse,
parseWorkspaceResponse,
} from "../src/lib/workspace/api/workspace-model.ts";
@@ -55,6 +56,24 @@ Deno.test("stale repository aliases fail closed at the JSON boundary", () => {
);
});
Deno.test("repository API result converts stale payloads into bounded page errors", () => {
const result = parseRepositoryListApiResult({
data: {
workspace_id: "w-a",
items: { main: repositoryList.items[0] },
source: "workspace-control-plane",
diagnostics: [],
},
error: null,
});
if (result.data !== null) {
throw new Error("stale payload must not reach the page");
}
if (!result.error?.includes("items must be an array")) {
throw new Error(`unexpected bounded error: ${result.error}`);
}
});
Deno.test("workspace response requires the permission projection", () => {
const stale = {
workspace_id: "w-a",