89 lines
2.8 KiB
TypeScript
89 lines
2.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
|
|
import type { Connector, ConnectorBinding, ConnectorCandidate } from "@/types/appops";
|
|
import { loadSetupConnectorContext } from "./setupConnectorContext.ts";
|
|
|
|
const connector = (id: string): Connector => ({
|
|
id,
|
|
name: id,
|
|
status: "connected",
|
|
lastTest: new Date(0).toISOString(),
|
|
capabilities: [],
|
|
type: "Media",
|
|
});
|
|
|
|
const candidate = (connectorId: string): ConnectorCandidate => ({
|
|
connectorId,
|
|
discoveredContainerId: connectorId,
|
|
containerId: connectorId,
|
|
containerName: connectorId,
|
|
displayName: connectorId,
|
|
appType: connectorId,
|
|
lifecycleState: "running",
|
|
suggestedAppId: connectorId,
|
|
connectorStatus: "connected",
|
|
missingSecrets: [],
|
|
requiredSecrets: [],
|
|
confidence: 0.99,
|
|
reasons: ["matched"],
|
|
ports: [],
|
|
});
|
|
|
|
describe("loadSetupConnectorContext", () => {
|
|
it("retries transient candidate failures before marking setup context partial", async () => {
|
|
let candidateAttempts = 0;
|
|
|
|
const result = await loadSetupConnectorContext([connector("plex")], {
|
|
getCandidates: async (): Promise<ConnectorCandidate[]> => {
|
|
candidateAttempts += 1;
|
|
if (candidateAttempts === 1) throw new Error("temporary busy");
|
|
return [candidate("plex")];
|
|
},
|
|
getBindings: async (): Promise<ConnectorBinding[]> => [],
|
|
});
|
|
|
|
assert.equal(candidateAttempts, 2);
|
|
assert.equal(result.partial, false);
|
|
assert.equal(result.candidates.plex.length, 1);
|
|
assert.deepEqual(result.bindings.plex, []);
|
|
});
|
|
|
|
it("keeps partial true when retry still fails", async () => {
|
|
const result = await loadSetupConnectorContext([connector("tautulli")], {
|
|
getCandidates: async (): Promise<ConnectorCandidate[]> => {
|
|
throw new Error("still down");
|
|
},
|
|
getBindings: async (): Promise<ConnectorBinding[]> => [],
|
|
});
|
|
|
|
assert.equal(result.partial, true);
|
|
assert.deepEqual(result.candidates.tautulli, []);
|
|
assert.deepEqual(result.bindings.tautulli, []);
|
|
});
|
|
|
|
it("limits connector context concurrency to avoid backend pool exhaustion", async () => {
|
|
let active = 0;
|
|
let maxActive = 0;
|
|
const observe = async <T,>(value: T): Promise<T> => {
|
|
active += 1;
|
|
maxActive = Math.max(maxActive, active);
|
|
await new Promise(resolve => setTimeout(resolve, 1));
|
|
active -= 1;
|
|
return value;
|
|
};
|
|
|
|
const result = await loadSetupConnectorContext(
|
|
["authentik", "deluge", "plex", "tautulli", "sonarr"].map(connector),
|
|
{
|
|
getCandidates: async (connectorId): Promise<ConnectorCandidate[]> => observe([candidate(connectorId)]),
|
|
getBindings: async (): Promise<ConnectorBinding[]> => observe([]),
|
|
},
|
|
);
|
|
|
|
assert.equal(result.partial, false);
|
|
assert.equal(Object.keys(result.candidates).length, 5);
|
|
assert.equal(maxActive, 4);
|
|
});
|
|
});
|