fix(FN-2321): standardize loopback-gated integration test labeling

- Refine the dashboard loopback integration helper with documented helper contracts and centralized skipped-name formatting
- Keep loopback bind detection memoized so all gated integration suites share one environment probe
- Update loopback-gated dashboard suites to use consistent, explicit scope labels in skip output
- Refresh skipped-test inventory docs with the new audit commands and loopback helper usage details
This commit is contained in:
Fusion
2026-04-23 10:50:44 -07:00
committed by gsxdsm
parent 86fd24e1d0
commit d8e86ba4c4
5 changed files with 36 additions and 13 deletions

View File

@@ -5,6 +5,15 @@ type IntegrationTestCase = (name: string, fn: () => unknown | Promise<unknown>,
const LOOPBACK_SKIP_REASON = "loopback binding to 127.0.0.1 is unavailable in this environment";
/**
* Ensure skip output is auditable with both the standardized reason and the suite scope.
*
* Example: "... (skipped: loopback binding to 127.0.0.1 is unavailable in this environment; scope: websocket integration)"
*/
function formatLoopbackSkipName(testName: string, scope: string): string {
return `${testName} (skipped: ${LOOPBACK_SKIP_REASON}; scope: ${scope})`;
}
let loopbackBindingAvailablePromise: Promise<boolean> | null = null;
async function detectLoopbackBinding(): Promise<boolean> {
@@ -17,6 +26,10 @@ async function detectLoopbackBinding(): Promise<boolean> {
});
}
/**
* Memoize loopback probe results so all integration suites share a single bind check.
* This avoids creating redundant probe listeners during test startup.
*/
async function isLoopbackBindingAvailable(): Promise<boolean> {
if (!loopbackBindingAvailablePromise) {
loopbackBindingAvailablePromise = detectLoopbackBinding();
@@ -25,6 +38,12 @@ async function isLoopbackBindingAvailable(): Promise<boolean> {
return await loopbackBindingAvailablePromise;
}
/**
* Canonical gate for dashboard integration tests that require loopback binding.
*
* When loopback is unavailable, every skipped test name includes a standardized
* reason string and the provided suite scope for auditability in test output.
*/
export async function createLoopbackIntegrationTest(scope: string): Promise<IntegrationTestCase> {
const loopbackBindingAvailable = await isLoopbackBindingAvailable();
@@ -32,5 +51,5 @@ export async function createLoopbackIntegrationTest(scope: string): Promise<Inte
return (name, fn, timeout) => it(name, fn, timeout);
}
return (name, fn, timeout) => it.skip(`${name} (skipped: ${LOOPBACK_SKIP_REASON}; scope: ${scope})`, fn, timeout);
return (name, fn, timeout) => it.skip(formatLoopbackSkipName(name, scope), fn, timeout);
}

View File

@@ -17,7 +17,7 @@ vi.mock("../github-webhooks.js", async () => {
const mockGetGitHubAppConfig = vi.mocked(getGitHubAppConfig);
const webhookIntegrationTest = await createLoopbackIntegrationTest("GitHub webhook integration");
const webhookIntegrationTest = await createLoopbackIntegrationTest("server-webhook GitHub webhook integration");
class MockStore extends EventEmitter {
private tasks = new Map<string, Task>();

View File

@@ -7,7 +7,7 @@ import { WebSocketManager } from "../websocket.js";
import { InMemoryBadgePubSub, type BadgePubSub } from "../badge-pubsub.js";
import { createLoopbackIntegrationTest } from "./loopback-integration-test.js";
const websocketIntegrationTest = await createLoopbackIntegrationTest("websocket integration");
const websocketIntegrationTest = await createLoopbackIntegrationTest("websocket /api/ws integration");
class MockSocket extends EventEmitter {
readyState: number = WebSocket.OPEN;

View File

@@ -2,7 +2,7 @@ import express from "express";
import { describe, expect, it } from "vitest";
import { createLoopbackIntegrationTest } from "./__tests__/loopback-integration-test.js";
const staticAssetIntegrationTest = await createLoopbackIntegrationTest("static asset serving");
const staticAssetIntegrationTest = await createLoopbackIntegrationTest("server-static-assets integration");
describe("static asset serving", () => {
staticAssetIntegrationTest("returns 404 for missing asset paths instead of falling back to index.html", async () => {