Files
kandev/apps/web/components/integrations/auth-status-banner.tsx
T
João Salavisa 5936bb6808 test: add e2e coverage and mock backends for jira and linear integrations (#771)
* test: add e2e coverage and mock backends for jira and linear integrations

Adds in-memory MockClient + MockController for both integrations gated by
KANDEV_MOCK_{JIRA,LINEAR}=true, plus apiClient mock helpers and Playwright
specs covering settings and import flows.

Also fixes ValidatedPopover's TriggerButton silently dropping radix Slot's
onClick handler — the popover wouldn't open under any synthetic-click path
(including Playwright). Spreads `...rest` onto the inner Button.

Reworks the integration cleanup hook in test-base as an auto fixture so it
runs for every test using the imported `test` object, not just tests in the
fixture file itself.

* fix: address PR review feedback on jira/linear mocks and e2e specs

- Fix SetAuthResult docstrings: nil simulates an unconfigured state, not the default success.
- Drop unused JSON tags from doneTransitionCall and setStateCall (Go-only test fixtures).
- Track Linear issue insertion order so SearchIssues returns deterministic results instead of map-iteration shuffle.
- Document filterByJQL precedence at the SearchTickets call site so the all-hits-on-no-match fallback is visible.
- Wait for the post-save auth-health probe before forcing a failure in the seeded-failure tests, eliminating a goroutine race.
- Reorder authMethod precedence in setJiraConfig/setLinearConfig to put the spread first so 'caller wins' reads naturally.

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-05-02 14:41:36 +01:00

77 lines
2.6 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { formatDistanceToNow } from "date-fns";
import { IconAlertTriangle, IconCheck } from "@tabler/icons-react";
import { Alert, AlertDescription } from "@kandev/ui/alert";
// AuthHealth captures everything every integration's config row tells us
// about the most-recent backend health probe. Each integration's settings
// page builds one of these from its own config shape.
export type IntegrationAuthHealth = {
ok: boolean;
error: string;
checkedAt: Date | null;
};
// Re-render every 30s so "checked 1 minute ago" doesn't sit stale on a long-
// open settings tab.
function useTick(intervalMs: number) {
const [, setTick] = useState(0);
useEffect(() => {
const id = setInterval(() => setTick((n) => n + 1), intervalMs);
return () => clearInterval(id);
}, [intervalMs]);
}
function LastCheckedLabel({ checkedAt }: { checkedAt: Date | null }) {
useTick(30_000);
if (!checkedAt) return null;
return (
<span className="text-xs text-muted-foreground ml-2">
· checked {formatDistanceToNow(checkedAt, { addSuffix: true })}
</span>
);
}
// IntegrationAuthStatusBanner renders the standard authenticated /
// authentication-failed / waiting alerts shown on every integration's
// settings page. Returns null when health is null (config not yet loaded or
// no secret configured) so the caller doesn't have to guard.
export function IntegrationAuthStatusBanner({ health }: { health: IntegrationAuthHealth | null }) {
if (!health) return null;
if (!health.checkedAt) {
return (
<Alert data-testid="integration-auth-status-banner" data-state="waiting">
<AlertDescription className="text-sm">
Waiting for the next backend health check…
</AlertDescription>
</Alert>
);
}
if (health.ok) {
return (
<Alert
data-testid="integration-auth-status-banner"
data-state="ok"
className="border-green-500/40 bg-green-500/10 dark:border-green-400/30 dark:bg-green-400/10"
>
<IconCheck className="h-4 w-4 text-green-600 dark:text-green-400" />
<AlertDescription className="text-sm font-medium">
Authenticated
<LastCheckedLabel checkedAt={health.checkedAt} />
</AlertDescription>
</Alert>
);
}
return (
<Alert data-testid="integration-auth-status-banner" data-state="failed" variant="destructive">
<IconAlertTriangle className="h-4 w-4" />
<AlertDescription className="text-sm">
Authentication failed: {health.error || "unknown error"}
<LastCheckedLabel checkedAt={health.checkedAt} />
</AlertDescription>
</Alert>
);
}