test: stabilize gateway wake gating regression

This commit is contained in:
Peter Steinberger
2026-04-13 03:23:51 -07:00
parent ca9f969831
commit 63965dc70b
2 changed files with 94 additions and 55 deletions

View File

@@ -110,6 +110,7 @@ const { STARTUP_UNAVAILABLE_GATEWAY_METHODS } =
await import("./server-startup-unavailable-methods.js");
type PostAttachParams = Parameters<typeof startGatewayPostAttachRuntime>[0];
type PostAttachRuntimeDeps = NonNullable<Parameters<typeof startGatewayPostAttachRuntime>[1]>;
describe("startGatewayPostAttachRuntime", () => {
beforeEach(() => {
@@ -144,35 +145,54 @@ describe("startGatewayPostAttachRuntime", () => {
});
it("keeps startup-gated methods unavailable while sidecars are still resuming", async () => {
let resumeChannels!: () => void;
const channelsReady = new Promise<void>((resolve) => {
resumeChannels = resolve;
let resumeSidecars!: () => void;
const sidecarsReady = new Promise<{ pluginServices: null }>((resolve) => {
resumeSidecars = () => resolve({ pluginServices: null });
});
const startChannels = vi.fn(async () => {
await channelsReady;
const startGatewaySidecars = vi.fn(async () => {
return await sidecarsReady;
});
const unavailableGatewayMethods = new Set<string>(STARTUP_UNAVAILABLE_GATEWAY_METHODS);
const startup = startGatewayPostAttachRuntime({
...createPostAttachParams({ startChannels }),
unavailableGatewayMethods,
});
const startup = startGatewayPostAttachRuntime(
{
...createPostAttachParams(),
unavailableGatewayMethods,
},
createPostAttachRuntimeDeps({ startGatewaySidecars }),
);
await vi.waitFor(() => {
expect(startChannels).toHaveBeenCalledTimes(1);
});
await vi.waitFor(
() => {
expect(startGatewaySidecars).toHaveBeenCalledTimes(1);
},
{ timeout: 10_000 },
);
expect([...unavailableGatewayMethods]).toEqual([...STARTUP_UNAVAILABLE_GATEWAY_METHODS]);
expect(hoisted.startPluginServices).not.toHaveBeenCalled();
resumeChannels();
resumeSidecars();
await startup;
expect([...unavailableGatewayMethods]).toEqual([]);
expect(hoisted.startPluginServices).toHaveBeenCalledTimes(1);
expect(startGatewaySidecars).toHaveBeenCalledTimes(1);
});
});
function createPostAttachRuntimeDeps(
overrides: Partial<PostAttachRuntimeDeps> = {},
): PostAttachRuntimeDeps {
return {
getGlobalHookRunner: vi.fn(() => null),
logGatewayStartup: hoisted.logGatewayStartup,
scheduleGatewayUpdateCheck: hoisted.scheduleGatewayUpdateCheck,
startGatewaySidecars: vi.fn(async () => ({ pluginServices: null })),
startGatewayTailscaleExposure: hoisted.startGatewayTailscaleExposure,
...overrides,
};
}
function createPostAttachParams(overrides: Partial<PostAttachParams> = {}): PostAttachParams {
return {
minimalTestGateway: false,

View File

@@ -238,43 +238,62 @@ export async function startGatewaySidecars(params: {
return { pluginServices };
}
export async function startGatewayPostAttachRuntime(params: {
minimalTestGateway: boolean;
cfgAtStart: OpenClawConfig;
bindHost: string;
bindHosts: string[];
port: number;
tlsEnabled: boolean;
log: {
info: (msg: string) => void;
warn: (msg: string) => void;
};
isNixMode: boolean;
startupStartedAt?: number;
broadcast: (event: string, payload: unknown, opts?: { dropIfSlow?: boolean }) => void;
tailscaleMode: GatewayTailscaleMode;
resetOnExit: boolean;
controlUiBasePath: string;
logTailscale: {
info: (msg: string) => void;
warn: (msg: string) => void;
error: (msg: string) => void;
debug?: (msg: string) => void;
};
gatewayPluginConfigAtStart: OpenClawConfig;
pluginRegistry: ReturnType<typeof loadOpenClawPlugins>;
defaultWorkspaceDir: string;
deps: CliDeps;
startChannels: () => Promise<void>;
logHooks: {
info: (msg: string) => void;
warn: (msg: string) => void;
error: (msg: string) => void;
};
logChannels: { info: (msg: string) => void; error: (msg: string) => void };
unavailableGatewayMethods: Set<string>;
}) {
logGatewayStartup({
type GatewayPostAttachRuntimeDeps = {
getGlobalHookRunner: typeof getGlobalHookRunner;
logGatewayStartup: typeof logGatewayStartup;
scheduleGatewayUpdateCheck: typeof scheduleGatewayUpdateCheck;
startGatewaySidecars: typeof startGatewaySidecars;
startGatewayTailscaleExposure: typeof startGatewayTailscaleExposure;
};
const defaultGatewayPostAttachRuntimeDeps: GatewayPostAttachRuntimeDeps = {
getGlobalHookRunner,
logGatewayStartup,
scheduleGatewayUpdateCheck,
startGatewaySidecars,
startGatewayTailscaleExposure,
};
export async function startGatewayPostAttachRuntime(
params: {
minimalTestGateway: boolean;
cfgAtStart: OpenClawConfig;
bindHost: string;
bindHosts: string[];
port: number;
tlsEnabled: boolean;
log: {
info: (msg: string) => void;
warn: (msg: string) => void;
};
isNixMode: boolean;
startupStartedAt?: number;
broadcast: (event: string, payload: unknown, opts?: { dropIfSlow?: boolean }) => void;
tailscaleMode: GatewayTailscaleMode;
resetOnExit: boolean;
controlUiBasePath: string;
logTailscale: {
info: (msg: string) => void;
warn: (msg: string) => void;
error: (msg: string) => void;
debug?: (msg: string) => void;
};
gatewayPluginConfigAtStart: OpenClawConfig;
pluginRegistry: ReturnType<typeof loadOpenClawPlugins>;
defaultWorkspaceDir: string;
deps: CliDeps;
startChannels: () => Promise<void>;
logHooks: {
info: (msg: string) => void;
warn: (msg: string) => void;
error: (msg: string) => void;
};
logChannels: { info: (msg: string) => void; error: (msg: string) => void };
unavailableGatewayMethods: Set<string>;
},
runtimeDeps: GatewayPostAttachRuntimeDeps = defaultGatewayPostAttachRuntimeDeps,
) {
runtimeDeps.logGatewayStartup({
cfg: params.cfgAtStart,
bindHost: params.bindHost,
bindHosts: params.bindHosts,
@@ -290,7 +309,7 @@ export async function startGatewayPostAttachRuntime(params: {
const stopGatewayUpdateCheck = params.minimalTestGateway
? () => {}
: scheduleGatewayUpdateCheck({
: runtimeDeps.scheduleGatewayUpdateCheck({
cfg: params.cfgAtStart,
log: params.log,
isNixMode: params.isNixMode,
@@ -302,7 +321,7 @@ export async function startGatewayPostAttachRuntime(params: {
const tailscaleCleanup = params.minimalTestGateway
? null
: await startGatewayTailscaleExposure({
: await runtimeDeps.startGatewayTailscaleExposure({
tailscaleMode: params.tailscaleMode,
resetOnExit: params.resetOnExit,
port: params.port,
@@ -313,7 +332,7 @@ export async function startGatewayPostAttachRuntime(params: {
let pluginServices: PluginServicesHandle | null = null;
if (!params.minimalTestGateway) {
params.log.info("starting channels and sidecars...");
({ pluginServices } = await startGatewaySidecars({
({ pluginServices } = await runtimeDeps.startGatewaySidecars({
cfg: params.gatewayPluginConfigAtStart,
pluginRegistry: params.pluginRegistry,
defaultWorkspaceDir: params.defaultWorkspaceDir,
@@ -329,7 +348,7 @@ export async function startGatewayPostAttachRuntime(params: {
}
if (!params.minimalTestGateway) {
const hookRunner = getGlobalHookRunner();
const hookRunner = runtimeDeps.getGlobalHookRunner();
if (hookRunner?.hasHooks("gateway_start")) {
void hookRunner.runGatewayStart({ port: params.port }, { port: params.port }).catch((err) => {
params.log.warn(`gateway_start hook failed: ${String(err)}`);