Skip to content
Open
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
189 changes: 189 additions & 0 deletions apps/web/src/components/settings/DiagnosticsSettings.logic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { assert, describe, it } from "@effect/vitest";
import { EnvironmentId } from "@t3tools/contracts";

import {
addPendingProcessSignal,
diagnosticsConnectionNotice,
pendingProcessSignalPids,
removePendingProcessSignal,
resolveDiagnosticsEnvironmentId,
type PendingProcessSignal,
} from "./DiagnosticsSettings.logic";

const PRIMARY = EnvironmentId.make("primary");
const ACTIVE = EnvironmentId.make("active");
const SELECTED = EnvironmentId.make("selected");

describe("resolveDiagnosticsEnvironmentId", () => {
it("preserves an explicit available selection", () => {
assert.equal(
resolveDiagnosticsEnvironmentId({
selectedEnvironmentId: SELECTED,
primaryEnvironmentId: PRIMARY,
activeEnvironmentId: ACTIVE,
availableEnvironmentIds: [PRIMARY, ACTIVE, SELECTED],
}),
SELECTED,
);
});

it("defaults to the primary environment when one is available", () => {
assert.equal(
resolveDiagnosticsEnvironmentId({
selectedEnvironmentId: null,
primaryEnvironmentId: PRIMARY,
activeEnvironmentId: ACTIVE,
availableEnvironmentIds: [ACTIVE, PRIMARY],
}),
PRIMARY,
);
});

it("uses the active environment when there is no primary environment", () => {
assert.equal(
resolveDiagnosticsEnvironmentId({
selectedEnvironmentId: null,
primaryEnvironmentId: null,
activeEnvironmentId: ACTIVE,
availableEnvironmentIds: [SELECTED, ACTIVE],
}),
ACTIVE,
);
});

it("falls back to the first available environment", () => {
assert.equal(
resolveDiagnosticsEnvironmentId({
selectedEnvironmentId: null,
primaryEnvironmentId: null,
activeEnvironmentId: null,
availableEnvironmentIds: [SELECTED, ACTIVE],
}),
SELECTED,
);
});

it("recovers when the explicit selection is no longer available", () => {
assert.equal(
resolveDiagnosticsEnvironmentId({
selectedEnvironmentId: SELECTED,
primaryEnvironmentId: PRIMARY,
activeEnvironmentId: ACTIVE,
availableEnvironmentIds: [PRIMARY, ACTIVE],
}),
PRIMARY,
);
});

it("returns null when no environments are available", () => {
assert.isNull(
resolveDiagnosticsEnvironmentId({
selectedEnvironmentId: SELECTED,
primaryEnvironmentId: PRIMARY,
activeEnvironmentId: ACTIVE,
availableEnvironmentIds: [],
}),
);
});
});

describe("diagnosticsConnectionNotice", () => {
it("returns null while the environment is connected", () => {
assert.isNull(
diagnosticsConnectionNotice({ phase: "connected", label: "Laptop", error: null }),
);
});

it("explains that an offline environment cannot report diagnostics", () => {
assert.equal(
diagnosticsConnectionNotice({ phase: "offline", label: "Laptop", error: null }),
"Laptop is offline. Diagnostics load once it reconnects.",
);
});

it("explains that a never-connected environment cannot report diagnostics", () => {
assert.equal(
diagnosticsConnectionNotice({ phase: "available", label: "Laptop", error: null }),
"Laptop is not connected. Diagnostics load once it connects.",
);
});

it("includes the failure reason while reconnecting", () => {
assert.equal(
diagnosticsConnectionNotice({
phase: "reconnecting",
label: "Laptop",
error: "socket hang up",
}),
"Reconnecting to Laptop... Reason: socket hang up",
);
});

it("reports a blocked connection", () => {
assert.equal(
diagnosticsConnectionNotice({ phase: "error", label: "Laptop", error: "unauthorized" }),
"Could not connect to Laptop. Reason: unauthorized",
);
});

it("reports an initial connection attempt", () => {
assert.equal(
diagnosticsConnectionNotice({ phase: "connecting", label: "Laptop", error: null }),
"Connecting to Laptop...",
);
});
});

describe("pending process signals", () => {
it("tracks pending signals per environment and pid", () => {
const pending = addPendingProcessSignal(
addPendingProcessSignal([], { environmentId: PRIMARY, pid: 100 }),
{ environmentId: ACTIVE, pid: 200 },
);

assert.deepEqual([...pendingProcessSignalPids(pending, PRIMARY)], [100]);
assert.deepEqual([...pendingProcessSignalPids(pending, ACTIVE)], [200]);
assert.deepEqual([...pendingProcessSignalPids(pending, SELECTED)], []);
assert.deepEqual([...pendingProcessSignalPids(pending, null)], []);
});

it("does not duplicate a signal that is already pending", () => {
const pending = addPendingProcessSignal([{ environmentId: PRIMARY, pid: 100 }], {
environmentId: PRIMARY,
pid: 100,
});

assert.equal(pending.length, 1);
});

it("keeps the same pid pending in another environment", () => {
const pending = addPendingProcessSignal(
addPendingProcessSignal([], { environmentId: PRIMARY, pid: 100 }),
{ environmentId: ACTIVE, pid: 100 },
);

assert.deepEqual([...pendingProcessSignalPids(pending, PRIMARY)], [100]);
assert.deepEqual([...pendingProcessSignalPids(pending, ACTIVE)], [100]);
});

it("only clears the completed request, leaving other environments pending", () => {
const pending = addPendingProcessSignal(
addPendingProcessSignal([], { environmentId: PRIMARY, pid: 100 }),
{ environmentId: ACTIVE, pid: 200 },
);

const remaining = removePendingProcessSignal(pending, { environmentId: PRIMARY, pid: 100 });

assert.deepEqual([...pendingProcessSignalPids(remaining, PRIMARY)], []);
assert.deepEqual([...pendingProcessSignalPids(remaining, ACTIVE)], [200]);
});

it("ignores completions for signals that are no longer pending", () => {
const pending: ReadonlyArray<PendingProcessSignal> = [{ environmentId: PRIMARY, pid: 100 }];

assert.strictEqual(
removePendingProcessSignal(pending, { environmentId: ACTIVE, pid: 100 }),
pending,
);
});
});
104 changes: 104 additions & 0 deletions apps/web/src/components/settings/DiagnosticsSettings.logic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { EnvironmentConnectionPhase } from "@t3tools/client-runtime/connection";
import type { EnvironmentId } from "@t3tools/contracts";

export function resolveDiagnosticsEnvironmentId(input: {
readonly selectedEnvironmentId: EnvironmentId | null;
readonly primaryEnvironmentId: EnvironmentId | null;
readonly activeEnvironmentId: EnvironmentId | null;
readonly availableEnvironmentIds: ReadonlyArray<EnvironmentId>;
}): EnvironmentId | null {
const availableEnvironmentIds = new Set(input.availableEnvironmentIds);

if (
input.selectedEnvironmentId !== null &&
availableEnvironmentIds.has(input.selectedEnvironmentId)
) {
return input.selectedEnvironmentId;
}
if (
input.primaryEnvironmentId !== null &&
availableEnvironmentIds.has(input.primaryEnvironmentId)
) {
return input.primaryEnvironmentId;
}
if (
input.activeEnvironmentId !== null &&
availableEnvironmentIds.has(input.activeEnvironmentId)
) {
return input.activeEnvironmentId;
}
return input.availableEnvironmentIds[0] ?? null;
}

/**
* Diagnostics queries only run while the selected environment has a connected
* supervisor generation; otherwise they stay pending forever. Returns the
* message to show instead of a loading state, or `null` when diagnostics can
* actually be collected.
*/
export function diagnosticsConnectionNotice(input: {
readonly phase: EnvironmentConnectionPhase;
readonly label: string;
readonly error: string | null;
}): string | null {
switch (input.phase) {
case "connected":
return null;
case "connecting":
return `Connecting to ${input.label}...`;
case "reconnecting":
return input.error
? `Reconnecting to ${input.label}... Reason: ${input.error}`
: `Reconnecting to ${input.label}...`;
case "offline":
return `${input.label} is offline. Diagnostics load once it reconnects.`;
case "available":
return `${input.label} is not connected. Diagnostics load once it connects.`;
case "error":
return input.error
? `Could not connect to ${input.label}. Reason: ${input.error}`
: `Could not connect to ${input.label}.`;
}
}

/**
* An in-flight process signal. Signals are identified by environment as well as
* pid so that a completion in one environment cannot clear pending state that
* belongs to another one.
*/
export interface PendingProcessSignal {
readonly environmentId: EnvironmentId;
readonly pid: number;
}

function isSamePendingProcessSignal(left: PendingProcessSignal, right: PendingProcessSignal) {
return left.environmentId === right.environmentId && left.pid === right.pid;
}

export function addPendingProcessSignal(
pending: ReadonlyArray<PendingProcessSignal>,
signal: PendingProcessSignal,
): ReadonlyArray<PendingProcessSignal> {
return pending.some((entry) => isSamePendingProcessSignal(entry, signal))
? pending
: [...pending, signal];
}

export function removePendingProcessSignal(
pending: ReadonlyArray<PendingProcessSignal>,
signal: PendingProcessSignal,
): ReadonlyArray<PendingProcessSignal> {
const next = pending.filter((entry) => !isSamePendingProcessSignal(entry, signal));
return next.length === pending.length ? pending : next;
}

export function pendingProcessSignalPids(
pending: ReadonlyArray<PendingProcessSignal>,
environmentId: EnvironmentId | null,
): ReadonlySet<number> {
return new Set(
pending
.filter((entry) => environmentId !== null && entry.environmentId === environmentId)
.map((entry) => entry.pid),
);
}
Loading
Loading