refactor(tests+frontend): one http_sink helper for the hook tests; apiErrorMessage replaces ten hand-rolled error-body parses; type X, import specifiers are not definitions (#2904, milestone 299 step 6)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Failing after 9s
CI & Build / integration (push) Successful in 28s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 1m11s
CI & Build / Build & push image (push) Successful in 38s

tests/helpers.http_sink replaces three module-local _Sink handlers (the
write-path tests and the after-write test). ProjectView + SettingsView
parsed `(e as {body?:{error?}}).body?.error || fallback` by hand ten times
beside the apiErrorMessage canon (#2853) - all ten now call it. The
extractor (server + the hook awk mirror) no longer reads `import { type Foo }`
as a definition of Foo - that was the last "identical body" sym family.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 15:07:23 -04:00
co-authored by Claude Fable 5
parent 449f437048
commit 590203a293
8 changed files with 137 additions and 133 deletions
+2 -3
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { apiGet, apiPatch, apiDelete, apiPost, apiPut } from "@/api/client";
import { apiGet, apiPatch, apiDelete, apiPost, apiPut, apiErrorMessage } from "@/api/client";
import { useAuthStore } from "@/stores/auth";
import { useToastStore } from "@/stores/toast";
import { useTasksStore } from "@/stores/tasks";
@@ -543,8 +543,7 @@ async function saveForgePin() {
if (project.value) project.value.forge_connection_id = forgePin.value;
await loadCoverage();
} catch (e) {
const body = (e as { body?: { error?: string } }).body;
toast.show(body?.error || "Failed to change the project's forge", "error");
toast.show(apiErrorMessage(e, "Failed to change the project's forge"), "error");
forgePin.value = project.value?.forge_connection_id ?? null;
} finally {
savingForgePin.value = false;
+9 -18
View File
@@ -780,8 +780,7 @@ async function saveConnection() {
connFormOpen.value = false;
await loadForgeConnections();
} catch (e) {
const body = (e as { body?: { error?: string } }).body;
toastStore.show(body?.error || "Failed to save forge connection", "error");
toastStore.show(apiErrorMessage(e, "Failed to save forge connection"), "error");
} finally {
savingConn.value = false;
}
@@ -793,8 +792,7 @@ async function removeConnection(id: number) {
connTestResult.value = null;
await loadForgeConnections();
} catch (e) {
const body = (e as { body?: { error?: string } }).body;
toastStore.show(body?.error || "Failed to delete forge connection", "error");
toastStore.show(apiErrorMessage(e, "Failed to delete forge connection"), "error");
}
}
@@ -810,10 +808,9 @@ async function testConnection(id: number) {
message: `Connected — ${res.version}, authenticated as ${res.username}`,
};
} catch (e) {
const body = (e as { body?: { error?: string } }).body;
connTestResult.value = {
id, ok: false,
message: body?.error || "Connection test failed",
message: apiErrorMessage(e, "Connection test failed"),
};
} finally {
testingConnId.value = 0;
@@ -830,8 +827,7 @@ async function saveForgeWebhook() {
forgeWebhookSaved.value = true;
setTimeout(() => (forgeWebhookSaved.value = false), 2000);
} catch (e) {
const body = (e as { body?: { error?: string } }).body;
toastStore.show(body?.error || "Failed to save webhook secret", "error");
toastStore.show(apiErrorMessage(e, "Failed to save webhook secret"), "error");
} finally {
savingForgeWebhook.value = false;
}
@@ -864,8 +860,7 @@ async function saveMarketplaceUrl() {
marketplaceUrlSaved.value = true;
setTimeout(() => (marketplaceUrlSaved.value = false), 2000);
} catch (e) {
const body = (e as { body?: { error?: string } }).body;
toastStore.show(body?.error || "Failed to save marketplace URL", "error");
toastStore.show(apiErrorMessage(e, "Failed to save marketplace URL"), "error");
} finally {
savingMarketplaceUrl.value = false;
}
@@ -882,8 +877,7 @@ async function saveDbMaintenance() {
dbMaintSaved.value = true;
setTimeout(() => (dbMaintSaved.value = false), 2000);
} catch (e) {
const body = (e as { body?: { error?: string } }).body;
toastStore.show(body?.error || "Failed to save maintenance settings", "error");
toastStore.show(apiErrorMessage(e, "Failed to save maintenance settings"), "error");
} finally {
savingDbMaint.value = false;
}
@@ -912,8 +906,7 @@ async function runDbMaintenanceNow() {
);
await loadDbHealth(); // reflect the dead-tuple drop
} catch (e) {
const body = (e as { body?: { error?: string } }).body;
toastStore.show(body?.error || "Maintenance run failed", "error");
toastStore.show(apiErrorMessage(e, "Maintenance run failed"), "error");
} finally {
runningDbMaint.value = false;
}
@@ -1144,8 +1137,7 @@ async function sendInvite() {
inviteEmail.value = "";
await fetchInvitations();
} catch (e: unknown) {
const body = (e as { body?: { error?: string } })?.body;
toastStore.show(body?.error || "Failed to send invitation", "error");
toastStore.show(apiErrorMessage(e, "Failed to send invitation"), "error");
} finally {
sendingInvite.value = false;
}
@@ -1196,8 +1188,7 @@ async function deleteUser(userId: number) {
users.value = users.value.filter((u) => u.id !== userId);
toastStore.show("User deleted");
} catch (e: unknown) {
const body = (e as { body?: { error?: string } })?.body;
toastStore.show(body?.error || "Failed to delete user", "error");
toastStore.show(apiErrorMessage(e, "Failed to delete user"), "error");
} finally {
deleting.value = null;
}