fix(FN-2510): harden skills catalog fallback for short and invalid queries
- Update skills adapter fallback logic to handle empty and short catalog queries without surfacing 400 failures - Handle ApiRequestError paths in SkillsView so invalid-query responses degrade gracefully in the UI - Add regression coverage for adapter, routes, and SkillsView behavior across empty/short query cases - Document short-query catalog behavior in the dashboard guide and include a patch changeset for @runfusion/fusion
This commit is contained in:
@@ -63,15 +63,34 @@ export function SkillsView({ projectId, addToast, onClose }: SkillsViewProps) {
|
||||
|
||||
// Fetch catalog
|
||||
const loadCatalog = useCallback(async (query: string) => {
|
||||
const isCatalogUnavailableError = (error: unknown): boolean => {
|
||||
if (!error || typeof error !== "object") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const withStatus = error as { status?: unknown; details?: unknown };
|
||||
if (typeof withStatus.status === "number" && withStatus.status >= 500) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (withStatus.details && typeof withStatus.details === "object") {
|
||||
const details = withStatus.details as { code?: unknown };
|
||||
if (typeof details.code === "string" && details.code.startsWith("upstream_")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const legacy = error as { error?: unknown; code?: unknown };
|
||||
return typeof legacy.error === "string" && typeof legacy.code === "string";
|
||||
};
|
||||
|
||||
setIsLoadingCatalog(true);
|
||||
setCatalogError(null);
|
||||
try {
|
||||
const result = await fetchSkillsCatalog(query, 20, projectId);
|
||||
setCatalogEntries(result.entries);
|
||||
} catch (err) {
|
||||
// Check for upstream error with code (502 etc.)
|
||||
const error = err as { error?: string; code?: string };
|
||||
if (error.error && error.code) {
|
||||
if (isCatalogUnavailableError(err)) {
|
||||
setCatalogError("Catalog is temporarily unavailable. Please try again later.");
|
||||
} else {
|
||||
const message = err instanceof Error ? err.message : "Failed to load catalog";
|
||||
|
||||
@@ -336,11 +336,13 @@ describe("SkillsView", () => {
|
||||
});
|
||||
|
||||
describe("error handling", () => {
|
||||
it("shows error message for catalog fetch with upstream error", async () => {
|
||||
mockFetchSkillsCatalog.mockRejectedValue({
|
||||
error: "Service unavailable",
|
||||
code: "upstream_http_error",
|
||||
});
|
||||
it("shows friendly error message for catalog fetch with upstream ApiRequestError", async () => {
|
||||
mockFetchSkillsCatalog.mockRejectedValue(
|
||||
Object.assign(new Error("Upstream returned 400: Bad Request"), {
|
||||
status: 502,
|
||||
details: { code: "upstream_http_error" },
|
||||
})
|
||||
);
|
||||
|
||||
render(<SkillsView addToast={mockAddToast} onClose={onClose} />);
|
||||
|
||||
@@ -363,10 +365,12 @@ describe("SkillsView", () => {
|
||||
});
|
||||
|
||||
it("shows Try Again button for catalog error", async () => {
|
||||
mockFetchSkillsCatalog.mockRejectedValue({
|
||||
error: "Service unavailable",
|
||||
code: "upstream_http_error",
|
||||
});
|
||||
mockFetchSkillsCatalog.mockRejectedValue(
|
||||
Object.assign(new Error("Upstream returned 400: Bad Request"), {
|
||||
status: 502,
|
||||
details: { code: "upstream_http_error" },
|
||||
})
|
||||
);
|
||||
|
||||
render(<SkillsView addToast={mockAddToast} onClose={onClose} />);
|
||||
|
||||
@@ -569,10 +573,12 @@ describe("SkillsView", () => {
|
||||
|
||||
describe("error-state retry", () => {
|
||||
it("retry button is displayed when catalog fetch fails", async () => {
|
||||
mockFetchSkillsCatalog.mockRejectedValue({
|
||||
error: "Service unavailable",
|
||||
code: "upstream_http_error",
|
||||
});
|
||||
mockFetchSkillsCatalog.mockRejectedValue(
|
||||
Object.assign(new Error("Upstream returned 400: Bad Request"), {
|
||||
status: 502,
|
||||
details: { code: "upstream_http_error" },
|
||||
})
|
||||
);
|
||||
|
||||
render(<SkillsView addToast={mockAddToast} onClose={onClose} />);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user