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)
+})