test: run suite without split runner
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
import { describe, it, expect, mock, spyOn, beforeEach, afterEach, afterAll } from "bun:test"
|
||||
import { describe, it, expect, mock, spyOn, beforeEach, afterEach } from "bun:test"
|
||||
import type { RunResult } from "./types"
|
||||
import { createJsonOutputManager } from "./json-output"
|
||||
import { resolveSession } from "./session-resolver"
|
||||
import { executeOnCompleteHook } from "./on-complete-hook"
|
||||
import * as spawnWithWindowsHideModule from "../../shared/spawn-with-windows-hide"
|
||||
import type { OpencodeClient } from "./types"
|
||||
import * as originalSdk from "@opencode-ai/sdk"
|
||||
import * as originalPortUtils from "../../shared/port-utils"
|
||||
import { createServerConnectionWithDeps, type ServerConnectionDeps, type ServerConnectionOptions } from "./server-connection"
|
||||
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
|
||||
|
||||
type TestClient = { session: Record<string, unknown> }
|
||||
const mockServerClose = mock(() => {})
|
||||
const mockCreateOpencode = mock(() =>
|
||||
Promise.resolve({
|
||||
@@ -19,25 +19,23 @@ const mockCreateOpencode = mock(() =>
|
||||
const mockCreateOpencodeClient = mock(() => ({ session: {} }))
|
||||
const mockIsPortAvailable = mock(() => Promise.resolve(true))
|
||||
const mockGetAvailableServerPort = mock(() => Promise.resolve({ port: 9999, wasAutoSelected: false }))
|
||||
const mockWithWorkingOpencodePath = mock((startServer: () => Promise<unknown>) => startServer())
|
||||
const mockInjectServerAuthIntoClient = mock(() => {})
|
||||
|
||||
mock.module("@opencode-ai/sdk", () => ({
|
||||
createOpencode: mockCreateOpencode,
|
||||
createOpencodeClient: mockCreateOpencodeClient,
|
||||
}))
|
||||
function createDeps(): ServerConnectionDeps<TestClient> {
|
||||
return {
|
||||
createOpencode: mockCreateOpencode,
|
||||
createOpencodeClient: mockCreateOpencodeClient,
|
||||
isPortAvailable: mockIsPortAvailable,
|
||||
getAvailableServerPort: mockGetAvailableServerPort,
|
||||
withWorkingOpencodePath: mockWithWorkingOpencodePath,
|
||||
injectServerAuthIntoClient: mockInjectServerAuthIntoClient,
|
||||
}
|
||||
}
|
||||
|
||||
mock.module("../../shared/port-utils", () => ({
|
||||
isPortAvailable: mockIsPortAvailable,
|
||||
getAvailableServerPort: mockGetAvailableServerPort,
|
||||
DEFAULT_SERVER_PORT: 4096,
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("@opencode-ai/sdk", () => originalSdk)
|
||||
mock.module("../../shared/port-utils", () => originalPortUtils)
|
||||
mock.restore()
|
||||
})
|
||||
|
||||
const { createServerConnection } = await import("./server-connection")
|
||||
async function createServerConnection(options: ServerConnectionOptions) {
|
||||
return await createServerConnectionWithDeps(options, createDeps())
|
||||
}
|
||||
|
||||
interface MockWriteStream {
|
||||
write: (chunk: string) => boolean
|
||||
@@ -312,6 +310,10 @@ describe("integration: server connection", () => {
|
||||
mockCreateOpencode.mockClear()
|
||||
mockCreateOpencodeClient.mockClear()
|
||||
mockServerClose.mockClear()
|
||||
mockIsPortAvailable.mockClear()
|
||||
mockGetAvailableServerPort.mockClear()
|
||||
mockWithWorkingOpencodePath.mockClear()
|
||||
mockInjectServerAuthIntoClient.mockClear()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun:test"
|
||||
|
||||
import * as originalSdk from "@opencode-ai/sdk"
|
||||
import * as originalPortUtils from "../../shared/port-utils"
|
||||
import * as originalBinaryResolver from "./opencode-binary-resolver"
|
||||
import * as originalServerAuth from "../../shared/opencode-server-auth"
|
||||
import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test"
|
||||
import { createServerConnectionWithDeps, type ServerConnectionDeps, type ServerConnectionOptions } from "./server-connection"
|
||||
|
||||
const originalConsole = globalThis.console
|
||||
type TestClient = { session: Record<string, unknown>, baseUrl?: string }
|
||||
|
||||
const mockServerClose = mock(() => {})
|
||||
const mockCreateOpencode = mock(() =>
|
||||
@@ -24,34 +21,20 @@ const mockConsoleLog = mock(() => {})
|
||||
const mockWithWorkingOpencodePath = mock((startServer: () => Promise<unknown>) => startServer())
|
||||
const mockInjectServerAuthIntoClient = mock(() => {})
|
||||
|
||||
mock.module("@opencode-ai/sdk", () => ({
|
||||
createOpencode: mockCreateOpencode,
|
||||
createOpencodeClient: mockCreateOpencodeClient,
|
||||
}))
|
||||
function createDeps(): ServerConnectionDeps<TestClient> {
|
||||
return {
|
||||
createOpencode: mockCreateOpencode,
|
||||
createOpencodeClient: mockCreateOpencodeClient,
|
||||
isPortAvailable: mockIsPortAvailable,
|
||||
getAvailableServerPort: mockGetAvailableServerPort,
|
||||
withWorkingOpencodePath: mockWithWorkingOpencodePath,
|
||||
injectServerAuthIntoClient: mockInjectServerAuthIntoClient,
|
||||
}
|
||||
}
|
||||
|
||||
mock.module("../../shared/port-utils", () => ({
|
||||
isPortAvailable: mockIsPortAvailable,
|
||||
getAvailableServerPort: mockGetAvailableServerPort,
|
||||
DEFAULT_SERVER_PORT: 4096,
|
||||
}))
|
||||
|
||||
mock.module("./opencode-binary-resolver", () => ({
|
||||
withWorkingOpencodePath: mockWithWorkingOpencodePath,
|
||||
}))
|
||||
|
||||
mock.module("../../shared/opencode-server-auth", () => ({
|
||||
injectServerAuthIntoClient: mockInjectServerAuthIntoClient,
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("@opencode-ai/sdk", () => originalSdk)
|
||||
mock.module("../../shared/port-utils", () => originalPortUtils)
|
||||
mock.module("./opencode-binary-resolver", () => originalBinaryResolver)
|
||||
mock.module("../../shared/opencode-server-auth", () => originalServerAuth)
|
||||
mock.restore()
|
||||
})
|
||||
|
||||
const { createServerConnection } = await import("./server-connection")
|
||||
async function createServerConnection(options: ServerConnectionOptions) {
|
||||
return await createServerConnectionWithDeps(options, createDeps())
|
||||
}
|
||||
|
||||
describe("createServerConnection", () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createOpencode, createOpencodeClient } from "@opencode-ai/sdk"
|
||||
import { createOpencode as createOpencodeSdk, createOpencodeClient as createOpencodeClientSdk } from "@opencode-ai/sdk"
|
||||
import pc from "picocolors"
|
||||
import type { ServerConnection } from "./types"
|
||||
import { injectServerAuthIntoClient } from "../../shared/opencode-server-auth"
|
||||
@@ -7,6 +7,40 @@ import { withWorkingOpencodePath } from "./opencode-binary-resolver"
|
||||
|
||||
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1", "[::1]", "0.0.0.0"])
|
||||
|
||||
export type ServerConnectionOptions = {
|
||||
port?: number
|
||||
attach?: string
|
||||
signal: AbortSignal
|
||||
}
|
||||
|
||||
type OpencodeServer<TClient> = {
|
||||
client: TClient
|
||||
server: {
|
||||
url: string
|
||||
close: () => void
|
||||
}
|
||||
}
|
||||
|
||||
export type ServerConnectionDeps<TClient> = {
|
||||
createOpencode: (options: { signal: AbortSignal, port: number, hostname: string }) => Promise<OpencodeServer<TClient>>
|
||||
createOpencodeClient: (options: { baseUrl: string }) => TClient
|
||||
injectServerAuthIntoClient: (client: TClient) => void
|
||||
isPortAvailable: (port: number, hostname?: string) => Promise<boolean>
|
||||
getAvailableServerPort: (preferredPort?: number, hostname?: string) => Promise<{ port: number, wasAutoSelected: boolean }>
|
||||
withWorkingOpencodePath: (
|
||||
startServer: () => Promise<OpencodeServer<TClient>>,
|
||||
) => Promise<OpencodeServer<TClient>>
|
||||
}
|
||||
|
||||
const defaultDeps: ServerConnectionDeps<ServerConnection["client"]> = {
|
||||
createOpencode: createOpencodeSdk,
|
||||
createOpencodeClient: createOpencodeClientSdk,
|
||||
injectServerAuthIntoClient,
|
||||
isPortAvailable,
|
||||
getAvailableServerPort,
|
||||
withWorkingOpencodePath,
|
||||
}
|
||||
|
||||
function isLoopbackAttachUrl(url: string): boolean {
|
||||
try {
|
||||
const parsed = new URL(url)
|
||||
@@ -32,28 +66,30 @@ function isPortRangeExhausted(error: unknown): boolean {
|
||||
return error.message.includes("No available port found in range")
|
||||
}
|
||||
|
||||
async function startServer(options: { signal: AbortSignal, port: number }): Promise<ServerConnection> {
|
||||
async function startServer<TClient>(
|
||||
options: { signal: AbortSignal, port: number },
|
||||
deps: ServerConnectionDeps<TClient>,
|
||||
): Promise<{ client: TClient, cleanup: () => void }> {
|
||||
const { signal, port } = options
|
||||
const { client, server } = await withWorkingOpencodePath(() =>
|
||||
createOpencode({ signal, port, hostname: "127.0.0.1" }),
|
||||
const { client, server } = await deps.withWorkingOpencodePath(() =>
|
||||
deps.createOpencode({ signal, port, hostname: "127.0.0.1" }),
|
||||
)
|
||||
|
||||
console.log(pc.dim("Server listening at"), pc.cyan(server.url))
|
||||
return { client, cleanup: () => server.close() }
|
||||
}
|
||||
|
||||
export async function createServerConnection(options: {
|
||||
port?: number
|
||||
attach?: string
|
||||
signal: AbortSignal
|
||||
}): Promise<ServerConnection> {
|
||||
export async function createServerConnectionWithDeps<TClient>(
|
||||
options: ServerConnectionOptions,
|
||||
deps: ServerConnectionDeps<TClient>,
|
||||
): Promise<{ client: TClient, cleanup: () => void }> {
|
||||
const { port, attach, signal } = options
|
||||
|
||||
if (attach !== undefined) {
|
||||
console.log(pc.dim("Attaching to existing server at"), pc.cyan(attach))
|
||||
const client = createOpencodeClient({ baseUrl: attach })
|
||||
const client = deps.createOpencodeClient({ baseUrl: attach })
|
||||
if (isLoopbackAttachUrl(attach)) {
|
||||
injectServerAuthIntoClient(client)
|
||||
deps.injectServerAuthIntoClient(client)
|
||||
}
|
||||
return { client, cleanup: () => {} }
|
||||
}
|
||||
@@ -63,39 +99,39 @@ export async function createServerConnection(options: {
|
||||
throw new Error("Port must be between 1 and 65535")
|
||||
}
|
||||
|
||||
const available = await isPortAvailable(port, "127.0.0.1")
|
||||
const available = await deps.isPortAvailable(port, "127.0.0.1")
|
||||
|
||||
if (available) {
|
||||
console.log(pc.dim("Starting server on port"), pc.cyan(port.toString()))
|
||||
try {
|
||||
return await startServer({ signal, port })
|
||||
return await startServer({ signal, port }, deps)
|
||||
} catch (error) {
|
||||
if (!isPortStartFailure(error, port)) {
|
||||
throw error
|
||||
}
|
||||
|
||||
const stillAvailable = await isPortAvailable(port, "127.0.0.1")
|
||||
const stillAvailable = await deps.isPortAvailable(port, "127.0.0.1")
|
||||
if (stillAvailable) {
|
||||
throw error
|
||||
}
|
||||
|
||||
console.log(pc.dim("Port"), pc.cyan(port.toString()), pc.dim("became occupied, attaching to existing server"))
|
||||
const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}` })
|
||||
injectServerAuthIntoClient(client)
|
||||
const client = deps.createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}` })
|
||||
deps.injectServerAuthIntoClient(client)
|
||||
return { client, cleanup: () => {} }
|
||||
}
|
||||
}
|
||||
|
||||
console.log(pc.dim("Port"), pc.cyan(port.toString()), pc.dim("is occupied, attaching to existing server"))
|
||||
const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}` })
|
||||
injectServerAuthIntoClient(client)
|
||||
const client = deps.createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}` })
|
||||
deps.injectServerAuthIntoClient(client)
|
||||
return { client, cleanup: () => {} }
|
||||
}
|
||||
|
||||
let selectedPort: number
|
||||
let wasAutoSelected: boolean
|
||||
try {
|
||||
const selected = await getAvailableServerPort(DEFAULT_SERVER_PORT, "127.0.0.1")
|
||||
const selected = await deps.getAvailableServerPort(DEFAULT_SERVER_PORT, "127.0.0.1")
|
||||
selectedPort = selected.port
|
||||
wasAutoSelected = selected.wasAutoSelected
|
||||
} catch (error) {
|
||||
@@ -103,14 +139,14 @@ export async function createServerConnection(options: {
|
||||
throw error
|
||||
}
|
||||
|
||||
const defaultPortIsAvailable = await isPortAvailable(DEFAULT_SERVER_PORT, "127.0.0.1")
|
||||
const defaultPortIsAvailable = await deps.isPortAvailable(DEFAULT_SERVER_PORT, "127.0.0.1")
|
||||
if (defaultPortIsAvailable) {
|
||||
throw error
|
||||
}
|
||||
|
||||
console.log(pc.dim("Port range exhausted, attaching to existing server on"), pc.cyan(DEFAULT_SERVER_PORT.toString()))
|
||||
const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${DEFAULT_SERVER_PORT}` })
|
||||
injectServerAuthIntoClient(client)
|
||||
const client = deps.createOpencodeClient({ baseUrl: `http://127.0.0.1:${DEFAULT_SERVER_PORT}` })
|
||||
deps.injectServerAuthIntoClient(client)
|
||||
return { client, cleanup: () => {} }
|
||||
}
|
||||
|
||||
@@ -121,14 +157,18 @@ export async function createServerConnection(options: {
|
||||
}
|
||||
|
||||
try {
|
||||
return await startServer({ signal, port: selectedPort })
|
||||
return await startServer({ signal, port: selectedPort }, deps)
|
||||
} catch (error) {
|
||||
if (!isPortStartFailure(error, selectedPort)) {
|
||||
throw error
|
||||
}
|
||||
|
||||
const { port: retryPort } = await getAvailableServerPort(selectedPort + 1, "127.0.0.1")
|
||||
const { port: retryPort } = await deps.getAvailableServerPort(selectedPort + 1, "127.0.0.1")
|
||||
console.log(pc.dim("Retrying server start on port"), pc.cyan(retryPort.toString()))
|
||||
return await startServer({ signal, port: retryPort })
|
||||
return await startServer({ signal, port: retryPort }, deps)
|
||||
}
|
||||
}
|
||||
|
||||
export async function createServerConnection(options: ServerConnectionOptions): Promise<ServerConnection> {
|
||||
return await createServerConnectionWithDeps(options, defaultDeps)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user