openhands / src /api /no-direct-agent-server-calls.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
63522a5 verified
Raw History Blame Contribute Delete
2.71 kB
import { describe, expect, it } from "vitest";
import { readdirSync, readFileSync } from "node:fs";
import { join, relative } from "node:path";
const SRC_ROOT = join(process.cwd(), "src");
const EXCLUDED_SEGMENTS = new Set(["mocks", "routeTree.gen.ts"]);
const ALLOWED_AD_HOC_HTTP_FILES = new Set([
"api/automation-service/automation-service.api.ts",
"api/cloud/proxy.ts",
"api/main-app-auth.ts",
]);
function collectSourceFiles(dir: string): string[] {
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const fullPath = join(dir, entry.name);
// Normalize to forward slashes so the path matches ALLOWED_AD_HOC_HTTP_FILES
// entries on Windows where path.relative() returns backslash-separated paths.
const relPath = relative(SRC_ROOT, fullPath).replace(/\\/g, "/");
if (entry.isDirectory()) {
if (EXCLUDED_SEGMENTS.has(entry.name)) return [];
return collectSourceFiles(fullPath);
}
if (EXCLUDED_SEGMENTS.has(entry.name)) return [];
if (!/\.(ts|tsx)$/.test(entry.name)) return [];
if (/\.(test|spec)\.(ts|tsx)$/.test(entry.name)) return [];
return [relPath];
});
}
describe("agent-server API access", () => {
it("uses typed @openhands/typescript-client access instead of ad-hoc HTTP", () => {
const violations = collectSourceFiles(SRC_ROOT).flatMap((relPath) => {
const source = readFileSync(join(SRC_ROOT, relPath), "utf8");
const fileViolations: string[] = [];
if (/openHands\s*\./.test(source)) {
fileViolations.push("uses the shared axios instance directly");
}
if (/\bcreateHttpClient\s*\(/.test(source)) {
fileViolations.push("uses createHttpClient directly");
}
if (
/from\s+["']@openhands\/typescript-client\/client\/http-client["']/.test(
source,
)
) {
fileViolations.push("imports the low-level SDK HttpClient directly");
}
if (/\bnew\s+HttpClient\s*\(/.test(source)) {
fileViolations.push("constructs HttpClient directly");
}
if (
(/\baxios\s*\(/.test(source) ||
/\baxios\s*\.\s*(?:create|get|post|put|patch|delete|request)\s*\(/.test(
source,
)) &&
!ALLOWED_AD_HOC_HTTP_FILES.has(relPath)
) {
fileViolations.push("uses axios directly for HTTP calls");
}
if (
/\bfetch\s*\([\s\S]{0,200}['"`]\/api\//.test(source) &&
!ALLOWED_AD_HOC_HTTP_FILES.has(relPath)
) {
fileViolations.push("calls an /api path with fetch directly");
}
return fileViolations.map((violation) => `${relPath}: ${violation}`);
});
expect(violations).toEqual([]);
});
});