diff --git a/lib/components/base-components/NormalComponent/NormalComponent.ts b/lib/components/base-components/NormalComponent/NormalComponent.ts index 99534b930..8d3a1753b 100644 --- a/lib/components/base-components/NormalComponent/NormalComponent.ts +++ b/lib/components/base-components/NormalComponent/NormalComponent.ts @@ -18,6 +18,7 @@ import { point3, rotation, schematic_manual_edit_conflict_warning, + unknown_error_finding_part, } from "circuit-json" import { decomposeTSR } from "transformation-matrix" import Debug from "debug" @@ -1443,8 +1444,37 @@ export class NormalComponent< }), ) - // Convert "Not found" to empty object before caching or returning - const supplierPartNumbers = result === "Not found" ? {} : result + // Validate the result format + if (typeof result === "string") { + // Check if it's an HTML error page or "Not found" + if (result.includes(" { - this._asyncSupplierPartNumbers = await supplierPartNumbersMaybePromise - this._markDirty("PartsEngineRender") + await supplierPartNumbersMaybePromise + .then((supplierPartNumbers) => { + this._asyncSupplierPartNumbers = supplierPartNumbers + this._markDirty("PartsEngineRender") + }) + .catch((error: Error) => { + // Log structured error to Circuit JSON + this._asyncSupplierPartNumbers = {} + const errorObj = unknown_error_finding_part.parse({ + type: "unknown_error_finding_part", + message: `Failed to fetch supplier part numbers for ${this.getString()}: ${error.message}`, + source_component_id: this.source_component_id, + subcircuit_id: this.getSubcircuit()?.subcircuit_id, + }) + db.unknown_error_finding_part.insert(errorObj) + this._markDirty("PartsEngineRender") + }) }) } diff --git a/package.json b/package.json index ed16fde80..b7b57c287 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "bun-match-svg": "0.0.12", "calculate-elbow": "^0.0.12", "chokidar-cli": "^3.0.0", - "circuit-json": "^0.0.288", + "circuit-json": "^0.0.291", "circuit-json-to-bpc": "^0.0.13", "circuit-json-to-connectivity-map": "^0.0.22", "circuit-json-to-gltf": "^0.0.31", diff --git a/tests/components/normal-components/parts-engine-error-handling1.test.tsx b/tests/components/normal-components/parts-engine-error-handling1.test.tsx new file mode 100644 index 000000000..695213117 --- /dev/null +++ b/tests/components/normal-components/parts-engine-error-handling1.test.tsx @@ -0,0 +1,65 @@ +import { test, expect } from "bun:test" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +test("parts engine handles errors gracefully and logs them to Circuit JSON", async () => { + // Test 1: HTML error page + const { circuit: circuit1 } = getTestFixture() + const htmlMock = { + findPart: async () => + "Internal Server Error", + } as any + + circuit1.add( + + + , + ) + await circuit1.renderUntilSettled() + + const sc1 = circuit1.db.source_component.list()[0] + expect(sc1.supplier_part_numbers).toEqual({}) + const errors1 = circuit1.db.unknown_error_finding_part.list() + expect(errors1.length).toBeGreaterThan(0) + expect(errors1[0].message).toContain("Failed to fetch supplier part numbers") + expect(errors1[0].message).toContain(" { + throw new Error("Network request failed") + }, + } + + circuit2.add( + + + , + ) + await circuit2.renderUntilSettled() + + const sc2 = circuit2.db.source_component.list()[0] + expect(sc2.supplier_part_numbers).toEqual({}) + const errors2 = circuit2.db.unknown_error_finding_part.list() + expect(errors2.length).toBeGreaterThan(0) + expect(errors2[0].message).toContain("Network request failed") + + // Test 3: Invalid format (array instead of object) + const { circuit: circuit3 } = getTestFixture() + const invalidMock = { + findPart: async () => ["123-456", "789-012"], + } as any + + circuit3.add( + + + , + ) + await circuit3.renderUntilSettled() + + const sc3 = circuit3.db.source_component.list()[0] + expect(sc3.supplier_part_numbers).toEqual({}) + const errors3 = circuit3.db.unknown_error_finding_part.list() + expect(errors3.length).toBeGreaterThan(0) + expect(errors3[0].message).toContain("Invalid supplier part numbers format") +}) diff --git a/tests/components/normal-components/parts-engine-error-handling2.test.tsx b/tests/components/normal-components/parts-engine-error-handling2.test.tsx new file mode 100644 index 000000000..31428ad54 --- /dev/null +++ b/tests/components/normal-components/parts-engine-error-handling2.test.tsx @@ -0,0 +1,46 @@ +import { test, expect } from "bun:test" +import { getTestFixture } from "tests/fixtures/get-test-fixture" + +test("parts engine processes valid responses and special cases correctly", async () => { + // Test 1: Valid supplier part numbers + const { circuit: circuit1 } = getTestFixture() + const validMock = { + findPart: async () => ({ + digikey: ["123-456"], + mouser: ["789-012"], + }), + } + + circuit1.add( + + + , + ) + await circuit1.renderUntilSettled() + + const sc1 = circuit1.db.source_component.list()[0] + expect(sc1.supplier_part_numbers).toEqual({ + digikey: ["123-456"], + mouser: ["789-012"], + }) + const errors1 = circuit1.db.unknown_error_finding_part.list() + expect(errors1.length).toBe(0) + + // Test 2: "Not found" response (valid, converts to empty object) + const { circuit: circuit2 } = getTestFixture() + const notFoundMock = { + findPart: async () => "Not found", + } as any + + circuit2.add( + + + , + ) + await circuit2.renderUntilSettled() + + const sc2 = circuit2.db.source_component.list()[0] + expect(sc2.supplier_part_numbers).toEqual({}) + const errors2 = circuit2.db.unknown_error_finding_part.list() + expect(errors2.length).toBe(0) +})