diff --git a/internal/cloud/hwpod-node-ops.test.ts b/internal/cloud/hwpod-node-ops.test.ts index bd698599..6a929367 100644 --- a/internal/cloud/hwpod-node-ops.test.ts +++ b/internal/cloud/hwpod-node-ops.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { test } from "bun:test"; @@ -109,6 +109,39 @@ test("cloud-api discovers preinstalled workspace hwpod-specs without hardcoded d } }); +test("cloud-api discovers configmap-mounted registry hwpod-spec symlinks", async () => { + const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-spec-registry-")); + const registryDir = path.join(root, "registry"); + const dataDir = path.join(root, "data"); + await mkdir(registryDir, { recursive: true }); + await mkdir(dataDir, { recursive: true }); + const targetPath = path.join(dataDir, "constart-71freq-c.yaml"); + await writeFile(targetPath, sampleConstartSpecYaml(), "utf8"); + await symlink(targetPath, path.join(registryDir, "constart-71freq-c.yaml")); + const server = createCloudApiServer({ + env: { + PATH: process.env.PATH, + HWLAB_CODE_AGENT_WORKSPACE: root, + HWLAB_HWPOD_SPEC_REGISTRY_DIRS: registryDir + } + }); + await listen(server); + try { + const response = await fetch(`${serverUrl(server)}/v1/hwpod/specs`); + const payload = await response.json(); + assert.equal(response.status, 200); + assert.equal(payload.contractVersion, "hwpod-spec-discovery-v1"); + const spec = payload.specs.find((item: any) => item.name === "constart-71freq-c"); + assert.ok(spec); + assert.equal(spec.uid, "CONSTART-71FREQ-C"); + assert.equal(spec.nodeId, "node-d601-f103-v2"); + assert.equal(spec.workspacePath, "F:\\Work\\ConStart"); + } finally { + await close(server); + await rm(root, { recursive: true, force: true }); + } +}); + test("cloud-api probes discovered hwpod-spec availability through node-ops", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-hwpod-spec-probe-")); const specDir = path.join(root, ".hwlab"); @@ -379,6 +412,32 @@ spec: `; } +function sampleConstartSpecYaml() { + return `apiVersion: hwlab.dev/v0alpha1 +kind: Hwpod +metadata: + uid: CONSTART-71FREQ-C + name: constart-71freq-c +spec: + targetDevice: + board: ConStart 71-FREQ Controller + mcu: STM32F103 + workspace: + path: 'F:\\Work\\ConStart' + toolchain: keil-mdk + debugProbe: + type: daplink + serialNumber: 3FD750C63E342E24 + ioProbe: + uart: + port: COM4 + baudRate: 921600 + nodeBinding: + nodeId: node-d601-f103-v2 + nodeType: pc-host +`; +} + async function listen(server: any) { await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); } diff --git a/internal/cloud/hwpod-spec-discovery.ts b/internal/cloud/hwpod-spec-discovery.ts index 0f72f16f..129494e8 100644 --- a/internal/cloud/hwpod-spec-discovery.ts +++ b/internal/cloud/hwpod-spec-discovery.ts @@ -97,7 +97,7 @@ async function candidateRegistrySpecPaths(dirs: string[]) { try { const entries = await readdir(dir, { withFileTypes: true }); for (const entry of entries) { - if (!entry.isFile()) continue; + if (!entry.isFile() && !entry.isSymbolicLink()) continue; if (!/\.ya?ml$/iu.test(entry.name)) continue; paths.push(path.join(dir, entry.name)); }