Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/app/api/session-pools/[provider]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { NextResponse } from "next/server";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { getWebSessionPoolHealth } from "@omniroute/open-sse/services/webSessionPoolHealth";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";

export const dynamic = "force-dynamic";

export async function GET(
request: Request,
{ params }: { params: Promise<{ provider: string }> }
): Promise<Response> {
const authError = await requireManagementAuth(request);
if (authError) return authError;

const { provider } = await params;

try {
const report = getWebSessionPoolHealth(provider);
const poolData = report.providers[0];

if (!poolData) {
return NextResponse.json(
{ error: `No session pool found for provider '${provider}'` },
{ status: 404 }
);
}
Comment on lines +21 to +26

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current check if (!poolData) will never evaluate to true because getWebSessionPoolHealth(provider) always returns a provider entry (even if the provider has no active session pool, in which case poolData.pool is null). This causes the endpoint to return 200 OK with null pool data instead of the expected 404 Not Found for nonexistent or unregistered providers.

Updating the condition to check poolData.pool === null ensures that a 404 is correctly returned when no session pool exists for the requested provider.

Suggested change
if (!poolData) {
return NextResponse.json(
{ error: `No session pool found for provider '${provider}'` },
{ status: 404 }
);
}
if (!poolData || poolData.pool === null) {
return NextResponse.json(
{ error: "No session pool found for provider '" + provider + "'" },
{ status: 404 }
);
}


return NextResponse.json({
checkedAt: report.checkedAt,
...poolData,
});
} catch (err) {
return NextResponse.json(
{ error: sanitizeErrorMessage(err) || "Failed to get session pool health" },
{ status: 500 }
);
}
}
21 changes: 21 additions & 0 deletions src/app/api/session-pools/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NextResponse } from "next/server";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { getWebSessionPoolHealth } from "@omniroute/open-sse/services/webSessionPoolHealth";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";

export const dynamic = "force-dynamic";

export async function GET(request: Request): Promise<Response> {
const authError = await requireManagementAuth(request);
if (authError) return authError;

try {
const report = getWebSessionPoolHealth();
return NextResponse.json(report);
} catch (err) {
return NextResponse.json(
{ error: sanitizeErrorMessage(err) || "Failed to get session pool health" },
{ status: 500 }
);
}
}
233 changes: 233 additions & 0 deletions tests/unit/session-pool-rest-api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/**
* Session Pool REST API — Unit Tests (PR7 of issue #3368)
*
* Run: node --import tsx/esm --test tests/unit/session-pool-rest-api.test.ts
*/

import { describe, it } from "node:test";
import assert from "node:assert/strict";
import type { WebSessionPoolHealthDeps } from "../../open-sse/services/webSessionPoolHealth.ts";
import { getWebSessionPoolHealth } from "../../open-sse/services/webSessionPoolHealth.ts";

function createMockDeps(overrides: {
providers?: string[];
stats?: Record<string, any>;
sessionDetails?: Record<string, any[]>;
breakerCooldown?: Record<string, boolean>;
breakerRemaining?: Record<string, number | null>;
breakerStates?: Record<string, any>;
} = {}): WebSessionPoolHealthDeps {
const providers = overrides.providers ?? [];
const stats = overrides.stats ?? {};
const sessionDetails = overrides.sessionDetails ?? {};
const breakerCooldown = overrides.breakerCooldown ?? {};
const breakerRemaining = overrides.breakerRemaining ?? {};
const breakerStates = overrides.breakerStates ?? {};

return {
listProviders: () => [...providers],
getStats: (p: string) => stats[p] ?? null,
getSessionDetails: (p: string) => sessionDetails[p] ?? null,
isProviderInCooldown: (p: string) => breakerCooldown[p] ?? false,
getProviderCooldownRemainingMs: (p: string) => breakerRemaining[p] ?? null,
getProviderBreakerState: (p: string) => breakerStates[p] ?? null,
};
}

describe("GET /api/session-pools (list all)", () => {
it("returns empty report when no pools registered", () => {
const deps = createMockDeps();
const report = getWebSessionPoolHealth(undefined, deps);
assert.equal(report.providers.length, 0);
assert.ok(report.checkedAt);
});

it("returns all registered pools", () => {
const now = Date.now();
const deps = createMockDeps({
providers: ["pollinations", "longcat"],
stats: {
pollinations: {
provider: "pollinations",
sessions: { total: 6, active: 5, cooldown: 1, dead: 0 },
requests: { total: 100, success: 95, rate429: 3, otherErrors: 2 },
throughput: "12.3 req/s",
successRate: "95.0%",
elapsed: "2h 15m",
createdAt: now - 2 * 60 * 60 * 1000,
},
longcat: {
provider: "longcat",
sessions: { total: 4, active: 4, cooldown: 0, dead: 0 },
requests: { total: 50, success: 50, rate429: 0, otherErrors: 0 },
throughput: "8.0 req/s",
successRate: "100.0%",
elapsed: "1h",
createdAt: now - 60 * 60 * 1000,
},
},
sessionDetails: { pollinations: [], longcat: [] },
breakerStates: {
pollinations: { state: "CLOSED", failureCount: 0, lastFailureTime: null, retryAfterMs: 0 },
longcat: { state: "CLOSED", failureCount: 0, lastFailureTime: null, retryAfterMs: 0 },
},
});

const report = getWebSessionPoolHealth(undefined, deps);
assert.equal(report.providers.length, 2);
assert.equal(report.providers[0].provider, "pollinations");
assert.equal(report.providers[1].provider, "longcat");
});

it("includes health summary for each pool", () => {
const deps = createMockDeps({
providers: ["pollinations"],
stats: {
pollinations: {
provider: "pollinations",
sessions: { total: 6, active: 5, cooldown: 1, dead: 0 },
requests: { total: 100, success: 95, rate429: 3, otherErrors: 2 },
throughput: "12.3 req/s",
successRate: "95.0%",
elapsed: "2h",
createdAt: Date.now() - 2 * 60 * 60 * 1000,
},
},
sessionDetails: { pollinations: [] },
breakerStates: {
pollinations: { state: "CLOSED", failureCount: 0, lastFailureTime: null, retryAfterMs: 0 },
},
});

const report = getWebSessionPoolHealth(undefined, deps);
const pool = report.providers[0];
assert.ok(pool.pool);
assert.equal(pool.pool.totalSessions, 6);
assert.equal(pool.pool.activeSessions, 5);
assert.equal(pool.health, "healthy");
});
});

describe("GET /api/session-pools/[provider] (single pool)", () => {
it("returns specific pool when provider arg given", () => {
const deps = createMockDeps({
providers: ["pollinations", "longcat"],
stats: {
pollinations: {
provider: "pollinations",
sessions: { total: 6, active: 5, cooldown: 1, dead: 0 },
requests: { total: 100, success: 95, rate429: 3, otherErrors: 2 },
throughput: "12.3 req/s",
successRate: "95.0%",
elapsed: "2h",
createdAt: Date.now() - 2 * 60 * 60 * 1000,
},
},
sessionDetails: { pollinations: [] },
breakerStates: {
pollinations: { state: "CLOSED", failureCount: 0, lastFailureTime: null, retryAfterMs: 0 },
},
});

const report = getWebSessionPoolHealth("pollinations", deps);
assert.equal(report.providers.length, 1);
assert.equal(report.providers[0].provider, "pollinations");
});

it("returns pool with session details", () => {
const deps = createMockDeps({
providers: ["pollinations"],
stats: {
pollinations: {
provider: "pollinations",
sessions: { total: 2, active: 2, cooldown: 0, dead: 0 },
requests: { total: 10, success: 10, rate429: 0, otherErrors: 0 },
throughput: "5.0 req/s",
successRate: "100.0%",
elapsed: "30m",
createdAt: Date.now() - 30 * 60 * 1000,
},
},
sessionDetails: {
pollinations: [
{
id: "s1",
fingerprint: "fp-1",
status: "active",
totalRequests: 5,
successfulRequests: 5,
successRate: "100.0%",
inflight: 0,
cooldownRemaining: "none",
age: "30m",
},
{
id: "s2",
fingerprint: "fp-2",
status: "active",
totalRequests: 5,
successfulRequests: 5,
successRate: "100.0%",
inflight: 1,
cooldownRemaining: "none",
age: "30m",
},
],
},
breakerStates: {
pollinations: { state: "CLOSED", failureCount: 0, lastFailureTime: null, retryAfterMs: 0 },
},
});

const report = getWebSessionPoolHealth("pollinations", deps);
const pool = report.providers[0];
assert.equal(pool.sessions.length, 2);
assert.equal(pool.sessions[0].id, "s1");
assert.equal(pool.sessions[1].id, "s2");
});

it("returns pool with breaker state when available", () => {
const now = Date.now();
const deps = createMockDeps({
providers: ["pollinations"],
stats: {
pollinations: {
provider: "pollinations",
sessions: { total: 6, active: 0, cooldown: 2, dead: 4 },
requests: { total: 50, success: 10, rate429: 30, otherErrors: 10 },
throughput: "0.5 req/s",
successRate: "20.0%",
elapsed: "1h",
createdAt: now - 60 * 60 * 1000,
},
},
sessionDetails: { pollinations: [] },
breakerCooldown: { pollinations: true },
breakerRemaining: { pollinations: 30000 },
breakerStates: {
pollinations: {
state: "OPEN",
failureCount: 10,
lastFailureTime: now - 5000,
retryAfterMs: 30000,
},
},
});

const report = getWebSessionPoolHealth("pollinations", deps);
const pool = report.providers[0];
assert.ok(pool.breaker);
assert.equal(pool.breaker.state, "OPEN");
assert.equal(pool.breaker.inCooldown, true);
assert.equal(pool.breaker.cooldownRemainingMs, 30000);
assert.equal(pool.health, "down");
});

it("returns provider entry even when no pool registered (for 404 detection)", () => {
const deps = createMockDeps();
const report = getWebSessionPoolHealth("nonexistent", deps);
assert.equal(report.providers.length, 1);
assert.equal(report.providers[0].provider, "nonexistent");
assert.equal(report.providers[0].pool, null);
});
});