const assert = require('assert'); const fs = require('fs'); const path = require('path'); const root = path.resolve(__dirname, '..'); const backendDir = path.join(root, 'backend'); const serverPy = fs.readFileSync(path.join(backendDir, 'server.py'), 'utf8'); assert( fs.existsSync(path.join(backendDir, 'layout_preview.py')), 'backend/layout_preview.py should generate SVG previews from saved layout YAML' ); assert( fs.existsSync(path.join(backendDir, 'pdk_registry.py')), 'backend/pdk_registry.py should resolve public PDK YAML/GDS assets' ); assert( fs.existsSync(path.join(backendDir, 'gds_builder.py')), 'backend/gds_builder.py should build hierarchical GDS from saved project YAML' ); assert( fs.existsSync(path.join(backendDir, 'routed_layout_preview.py')), 'backend/routed_layout_preview.py should create routed SVG previews through mxpic_router' ); assert( serverPy.includes('create_layout_svg_from_gds'), 'save-layout route should create a GDS-derived layout SVG preview' ); assert( serverPy.includes('create_routed_layout_svg'), 'save-layout route should use routed preview generation when links exist' ); assert( serverPy.includes('cell_routes_path') && serverPy.includes('write_route_points_sidecar') && serverPy.includes('.routes.yml'), 'save-layout should write a sidecar route-points file for manually drawn link guidance' ); assert( serverPy.includes('svg_url'), 'save-layout response should include an svg_url for the new layout tab' ); assert( serverPy.includes("@app.route('/api/projects//cells//layout.svg')"), 'server should expose a route for saved cell SVG previews' ); assert( serverPy.includes("@app.route('/api/build-gds'"), 'server should expose a Build GDS API route' ); assert( serverPy.includes("@app.route('/api/technologies///manifest'"), 'server should expose a technology manifest API route' ); assert( fs.existsSync(path.join(backendDir, 'technology_manifest.py')), 'backend/technology_manifest.py should read generated technology manifests' ); const techManifestPath = path.join(root, 'mxpic', 'PDKs', 'Silterra', 'EMO1_2ML_CU_Al_RDL', 'technology.yml'); assert( fs.existsSync(techManifestPath), 'Silterra technology.yml should be generated into the EDA PDK folder' ); const techManifest = fs.readFileSync(techManifestPath, 'utf8'); for (const xsection of ['strip', 'rib_low', 'metal_1', 'metal_2']) { assert(techManifest.includes(`${xsection}:`), `technology.yml should include ${xsection}`); } assert(techManifest.includes('family: optical'), 'technology.yml should classify optical xsections'); assert(techManifest.includes('family: electrical'), 'technology.yml should classify electrical xsections'); const layoutPreviewPy = fs.readFileSync(path.join(backendDir, 'layout_preview.py'), 'utf8'); assert( layoutPreviewPy.includes('read_gds') || layoutPreviewPy.includes('load_gds'), 'layout_preview.py should load public _BB.gds geometry, not draw only schematic boxes' ); assert( layoutPreviewPy.includes('_BB.gds') || layoutPreviewPy.includes('gds_path'), 'layout_preview.py should resolve public GDS assets for placed components' ); const gdsBuilderPy = fs.readFileSync(path.join(backendDir, 'gds_builder.py'), 'utf8'); assert( gdsBuilderPy.includes('_cells_have_links') && gdsBuilderPy.includes('Routed Build GDS requires mxpic_router'), 'Build GDS should not silently fall back to unrouted gdstk when links are present' ); const routerDir = path.resolve(root, '..', 'mxpic_router', 'mxpic_router'); if (fs.existsSync(routerDir)) { const routerLoaderPy = fs.readFileSync(path.join(routerDir, 'eda_loader.py'), 'utf8'); const routerBuilderPy = fs.readFileSync(path.join(routerDir, 'builder.py'), 'utf8'); assert( routerLoaderPy.includes('port_number: int = 1') && routerLoaderPy.includes('pitch: float = 10.0') && routerLoaderPy.includes('port_number=_int(element.get("port_number"'), 'mxpic_router loader should parse multi-port anchor metadata from exported elements' ); assert( routerBuilderPy.includes('for index in range(port_number):') && routerBuilderPy.includes('a{index + 1}') && routerBuilderPy.includes('b{index + 1}'), 'mxpic_router builder should register aN/bN pins for multi-port anchors' ); } assert( serverPy.includes('def scoped_pdk_root_for_project') && serverPy.includes('read_project_meta(project_name).get("technology")') && serverPy.includes('os.path.join(base_root, foundry, technology)'), 'backend should resolve a project-scoped PDK root from selected foundry/technology' ); assert( serverPy.includes('request.args.get(\'project\')') && serverPy.includes('scoped_pdk_root_for_project(project)'), 'library/component APIs should accept ?project= and search inside the selected technology folder' ); assert( serverPy.includes('__path__') && serverPy.includes('os.path.relpath(root, path_root)'), 'library tree leaves should preserve component paths relative to the role PDK root' );