fix: validate repository settings wire response

This commit is contained in:
2026-09-01 13:13:01 +09:00
parent e01b46b30a
commit 323f5dc09c
3 changed files with 64 additions and 31 deletions
@@ -1,9 +1,20 @@
<script lang="ts">
import { invalidateAll } from '$app/navigation';
import { workspaceApiPath, workspaceRoute } from '$lib/workspace/api/http';
import type { RepositorySourceKind } from '$lib/generated/workspace-api';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
function sourceLabel(kind: RepositorySourceKind): string {
if (kind === 'local_path' || kind === 'file') return 'Local';
if (kind === 'invalid') return 'Invalid';
return 'Remote Git';
}
function supportsRepositoryAccess(kind: RepositorySourceKind): boolean {
return kind === 'ssh' || kind === 'http' || kind === 'https';
}
let showAddRepository = $state(false);
let repositoryId = $state('');
let displayName = $state('');
@@ -122,22 +133,22 @@
</tr>
</thead>
<tbody>
{#each data.repositories.items as repository (repository.repository_id)}
{#each data.repositories.items as repository (repository.id)}
<tr>
<td>
<a class="inline-link" href={workspaceRoute(data.workspaceId, `/repositories/${encodeURIComponent(repository.repository_id)}`)}>
<a class="inline-link" href={workspaceRoute(data.workspaceId, `/repositories/${encodeURIComponent(repository.id)}`)}>
<strong>{repository.display_name}</strong>
</a>
<small><code>{repository.repository_id}</code></small>
<small><code>{repository.id}</code></small>
</td>
<td>
<span>{repository.source.kind === 'local_path' ? 'Local' : 'Remote Git'}</span>
<span>{sourceLabel(repository.source.kind)}</span>
<small><code>{repository.source.uri}</code></small>
</td>
<td>{repository.default_selector ?? '—'}</td>
<td>{repository.observed.status}</td>
<td>{repository.observed_status}</td>
<td>
{#if repository.source.kind === 'remote_git'}
{#if supportsRepositoryAccess(repository.source.kind)}
<a class="inline-link" href={workspaceRoute(data.workspaceId, '/settings/repository-access')}>Configure access</a>
{:else}
<span class="settings-muted-action">Not required</span>
@@ -1,34 +1,13 @@
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
import { parseRepositoryListResponse } from "$lib/workspace/api/workspace-model";
import type { PageLoad } from "./$types";
export type RepositorySource = {
kind: "local_path" | "remote_git";
uri: string;
};
export type RepositorySummary = {
repository_id: string;
display_name: string;
kind: string;
provider: string;
source: RepositorySource;
default_selector?: string | null;
observed: {
status: string;
observed_at?: string | null;
};
diagnostics: Array<{ code: string; message: string }>;
};
type RepositoryListResponse = {
items: RepositorySummary[];
diagnostics: Array<{ code: string; message: string }>;
};
export const load: PageLoad = async ({ fetch, params }) => {
const repositories = await loadJson<RepositoryListResponse>(
const repositories = await loadJson(
fetch,
workspaceApiPath(params.workspaceId, "/repositories"),
undefined,
parseRepositoryListResponse,
);
return {
workspaceId: params.workspaceId,
@@ -1,5 +1,6 @@
declare const Deno: {
test(name: string, fn: () => void | Promise<void>): void;
readTextFile(path: URL): Promise<string>;
};
import {
@@ -108,3 +109,45 @@ Deno.test("workspace response requires the permission projection", () => {
"permissions must be an object",
);
});
Deno.test("Repository settings consume the validated shared wire shape", async () => {
const [loadSource, pageSource] = await Promise.all([
Deno.readTextFile(
new URL(
"../src/routes/w/[workspaceId]/settings/repositories/+page.ts",
import.meta.url,
),
),
Deno.readTextFile(
new URL(
"../src/routes/w/[workspaceId]/settings/repositories/+page.svelte",
import.meta.url,
),
),
]);
for (
const token of [
"parseRepositoryListResponse",
"repository.id",
"repository.observed_status",
"sourceLabel(repository.source.kind)",
"supportsRepositoryAccess(repository.source.kind)",
]
) {
if (!loadSource.includes(token) && !pageSource.includes(token)) {
throw new Error(`Repository settings should include ${token}`);
}
}
for (
const staleToken of [
"repository.repository_id",
"repository.observed.status",
"repository.source.kind === 'remote_git'",
]
) {
if (loadSource.includes(staleToken) || pageSource.includes(staleToken)) {
throw new Error(`Repository settings must not use ${staleToken}`);
}
}
});