test-hostbridge-server.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. #!/usr/bin/env npx tsx
  2. import * as grpc from "@grpc/grpc-js"
  3. import { ReflectionService } from "@grpc/reflection"
  4. import * as health from "grpc-health-check"
  5. import * as os from "os"
  6. import { type DiffServiceServer, DiffServiceService } from "../src/generated/grpc-js/host/diff"
  7. import { type EnvServiceServer, EnvServiceService } from "../src/generated/grpc-js/host/env"
  8. import { type TestingServiceServer, TestingServiceService } from "../src/generated/grpc-js/host/testing"
  9. import { type WindowServiceServer, WindowServiceService } from "../src/generated/grpc-js/host/window"
  10. import { type WorkspaceServiceServer, WorkspaceServiceService } from "../src/generated/grpc-js/host/workspace"
  11. import { getPackageDefinition } from "./proto-utils.mjs"
  12. export async function startTestHostBridgeServer() {
  13. const server = new grpc.Server()
  14. // Set up health check
  15. const healthImpl = new health.HealthImplementation({ "": "SERVING" })
  16. healthImpl.addToServer(server)
  17. // Add host bridge services using the mock implementations
  18. server.addService(WorkspaceServiceService, createMockService<WorkspaceServiceServer>("WorkspaceService"))
  19. server.addService(WindowServiceService, createMockService<WindowServiceServer>("WindowService"))
  20. server.addService(EnvServiceService, createMockService<EnvServiceServer>("EnvService"))
  21. server.addService(DiffServiceService, createMockService<DiffServiceServer>("DiffService"))
  22. server.addService(TestingServiceService, createMockService<TestingServiceServer>("TestingService"))
  23. // Load package definition for reflection service
  24. const packageDefinition = await getPackageDefinition()
  25. // Filter service names to only include host services
  26. const hostBridgeServiceNames = Object.keys(packageDefinition).filter(
  27. (name) => name.startsWith("host.") || name.startsWith("grpc.health"),
  28. )
  29. const reflection = new ReflectionService(packageDefinition, {
  30. services: hostBridgeServiceNames,
  31. })
  32. reflection.addToServer(server)
  33. const bindAddress = process.env.HOST_BRIDGE_ADDRESS || `127.0.0.1:26041`
  34. server.bindAsync(bindAddress, grpc.ServerCredentials.createInsecure(), (err) => {
  35. if (err) {
  36. console.error(`Failed to bind test host bridge server to ${bindAddress}:`, err)
  37. process.exit(1)
  38. }
  39. server.start()
  40. console.log(`Test HostBridge gRPC server listening on ${bindAddress}`)
  41. })
  42. }
  43. /**
  44. * Creates a mock gRPC service implementation using Proxy
  45. * @param serviceName Name of the service for logging
  46. * @returns A proxy that implements the service interface
  47. */
  48. function createMockService<T extends grpc.UntypedServiceImplementation>(serviceName: string): T {
  49. const handler: ProxyHandler<T> = {
  50. get(_target, prop) {
  51. // Return a function that handles the gRPC call
  52. return (call: any, callback: any) => {
  53. console.log(`Hostbridge: ${serviceName}.${String(prop)} called with:`, call.request)
  54. // Special cases that need specific return values
  55. switch (prop) {
  56. case "getWorkspacePaths":
  57. const workspaceDir = process.env.TEST_HOSTBRIDGE_WORKSPACE_DIR || "/test-workspace"
  58. callback(null, {
  59. paths: [workspaceDir],
  60. })
  61. return
  62. case "getMachineId":
  63. callback(null, {
  64. value: "fake-machine-id-" + os.hostname(),
  65. })
  66. return
  67. case "getTelemetrySettings":
  68. callback(null, {
  69. isEnabled: 2, // Setting.DISABLED
  70. errorLevel: "all",
  71. })
  72. return
  73. case "clipboardReadText":
  74. callback(null, {
  75. value: "",
  76. })
  77. return
  78. case "getWebviewHtml":
  79. callback(null, {
  80. html: "<html><body>Fake Webview</body></html>",
  81. })
  82. return
  83. case "showTextDocument":
  84. callback(null, {
  85. document_path: call.request?.path || "",
  86. view_column: 1,
  87. is_active: true,
  88. })
  89. return
  90. case "openDiff":
  91. callback(null, {
  92. diff_id: "fake-diff-" + Date.now(),
  93. })
  94. return
  95. case "getDocumentText":
  96. callback(null, {
  97. content: "",
  98. })
  99. return
  100. case "getOpenTabs":
  101. case "getVisibleTabs":
  102. case "showOpenDialogue":
  103. callback(null, {
  104. paths: [],
  105. })
  106. return
  107. case "getDiagnostics":
  108. callback(null, {
  109. file_diagnostics: [],
  110. })
  111. return
  112. // For streaming methods (like subscribeToTelemetrySettings)
  113. case "subscribeToTelemetrySettings":
  114. // Just end the stream immediately
  115. call.end()
  116. return
  117. }
  118. // Default: return empty object for all other methods
  119. callback(null, {})
  120. }
  121. },
  122. }
  123. return new Proxy({} as T, handler)
  124. }
  125. if (require.main === module) {
  126. startTestHostBridgeServer().catch((err) => {
  127. console.error("Failed to start test host bridge server:", err)
  128. process.exit(1)
  129. })
  130. }