fabro/apps/fabro-web/app/routes/settings-integrations.test.tsx
Bryan Helmkamp ab0f94fd82
feat(system): report runtime integration status (#416)
## Summary

Settings > Integrations now reflects the server's actual integration
readiness instead of only static `settings.toml` booleans. This adds
`/api/v1/system/integrations` as the runtime source of truth, covering
server config, vault credential presence, and Slack Socket Mode
connection state.

## What Changed

- Added shared `fabro-types` integration status models and reused them
from `fabro-api` to avoid duplicate API/domain types.
- Added `GET /api/v1/system/integrations` to the OpenAPI spec, Rust
server routes, demo routes, and generated TypeScript client.
- Reports GitHub and Slack status as `disabled`, `missing_credentials`,
`configured`, `connecting`, `connected`, or `error`, with non-secret
metadata and missing credential names.
- Tracks Slack Socket Mode runtime state from the Slack connection loop
and respects explicit `server.integrations.slack.enabled = false` even
when vault tokens exist.
- Updated the Integrations settings page to read the new runtime
endpoint, so a vault-configured Slack setup no longer appears simply as
disabled.

## Verification

- `cargo build -p fabro-api`
- `cargo nextest run -p fabro-api system_integrations`
- `cargo nextest run -p fabro-config
resolved_server_integrations_are_slack_only_for_chat`
- `cargo nextest run -p fabro-slack
run_event_loop_notifies_connected_status`
- `cargo nextest run -p fabro-server --features test-support --test it
get_system_integrations`
- `cargo nextest run -p fabro-server`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cd apps/fabro-web && bun test
app/routes/settings-integrations.test.tsx app/lib/query-keys.test.ts`
- `cd apps/fabro-web && bun run typecheck`
- `cd apps/fabro-web && bun run build`

---

[![Compound
Engineering](https://img.shields.io/badge/Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
2026-05-26 19:27:32 -04:00

117 lines
3.4 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
import type {
SystemIntegrationsResponse,
SystemIntegrationStatus,
} from "@qltysh/fabro-api-client";
import TestRenderer, { act } from "react-test-renderer";
import { setupReactTestEnv } from "../lib/test-utils";
let systemIntegrations: SystemIntegrationsResponse | undefined;
let teardownReactTestEnv: (() => void) | undefined;
mock.module("../lib/queries", () => ({
useSystemIntegrations: () => ({ data: systemIntegrations }),
}));
const { default: SettingsIntegrations } = await import("./settings-integrations");
const mountedRenderers: TestRenderer.ReactTestRenderer[] = [];
function renderSettingsIntegrations() {
let renderer: TestRenderer.ReactTestRenderer | undefined;
act(() => {
renderer = TestRenderer.create(<SettingsIntegrations />);
});
mountedRenderers.push(renderer!);
return renderer!;
}
function textContent(node: ReturnType<TestRenderer.ReactTestRenderer["toJSON"]>): string {
if (node == null || typeof node === "boolean") return "";
if (typeof node === "string" || typeof node === "number") return String(node);
if (Array.isArray(node)) return node.map(textContent).join("");
return node.children?.map(textContent).join("") ?? "";
}
function sampleStatus(
overrides: Partial<SystemIntegrationStatus> = {},
): SystemIntegrationStatus {
return {
provider: "slack",
enabled: true,
configured: true,
status: "connected",
missing_credentials: [],
connection: {
kind: "socket_mode",
status: "connected",
last_connected_at: "2026-05-26T04:00:00Z",
last_error: null,
},
metadata: {},
...overrides,
};
}
function sampleIntegrations(
slack: Partial<SystemIntegrationStatus> = {},
): SystemIntegrationsResponse {
return {
data: [
sampleStatus({
provider: "github",
status: "configured",
connection: null,
metadata: { strategy: "app", slug: "fabro-sh" },
}),
sampleStatus({
metadata: { default_channel: "#fabro" },
...slack,
}),
],
};
}
describe("SettingsIntegrations route", () => {
beforeEach(() => {
teardownReactTestEnv = setupReactTestEnv();
});
afterEach(() => {
act(() => {
for (const renderer of mountedRenderers.splice(0)) {
renderer.unmount();
}
});
systemIntegrations = undefined;
teardownReactTestEnv?.();
teardownReactTestEnv = undefined;
});
test("renders Slack runtime connection status", () => {
systemIntegrations = sampleIntegrations();
const renderer = renderSettingsIntegrations();
const text = textContent(renderer.toJSON());
expect(text).toContain("Slack");
expect(text).toContain("Connected");
expect(text).toContain("channel: #fabro");
expect(text).not.toContain("Disabled");
});
test("renders missing Slack credential names", () => {
systemIntegrations = sampleIntegrations({
configured: false,
status: "missing_credentials",
missing_credentials: ["SLACK_APP_TOKEN", "SLACK_BOT_TOKEN"],
connection: null,
});
const renderer = renderSettingsIntegrations();
const text = textContent(renderer.toJSON());
expect(text).toContain("Missing credentials");
expect(text).toContain("missing: SLACK_APP_TOKEN, SLACK_BOT_TOKEN");
});
});