mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-13 12:17:34 +00:00
0cd03c6f87
Gate Routing, Secrets, Host Console, and Mesh dashboard/settings surfaces on the existing useExperimental readiness flag so immature operator surfaces stay out of the default UI while paid and admin backend gates remain unchanged.
122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import { renderHook, act } from '@testing-library/react';
|
|
|
|
const apiFetchMock = vi.fn();
|
|
|
|
vi.mock('@/lib/api', () => ({
|
|
apiFetch: (...args: unknown[]) => apiFetchMock(...args),
|
|
}));
|
|
|
|
const useLicenseMock = vi.fn();
|
|
vi.mock('@/context/LicenseContext', () => ({
|
|
useLicense: () => useLicenseMock(),
|
|
}));
|
|
|
|
const useExperimentalMock = vi.fn(() => ({ experimental: true, experimentalReady: true }));
|
|
vi.mock('@/hooks/useExperimental', () => ({
|
|
useExperimental: () => useExperimentalMock(),
|
|
}));
|
|
|
|
vi.mock('@/lib/utils', async () => {
|
|
const actual = await vi.importActual<typeof import('@/lib/utils')>('@/lib/utils');
|
|
return {
|
|
...actual,
|
|
visibilityInterval: () => () => {},
|
|
};
|
|
});
|
|
|
|
import { useMeshDataPlane } from '../useMeshDataPlane';
|
|
|
|
function okJson(payload: unknown): Response {
|
|
return new Response(JSON.stringify(payload), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
|
|
function statusJson(status: number, payload: unknown = {}): Response {
|
|
return new Response(JSON.stringify(payload), {
|
|
status,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
}
|
|
|
|
beforeEach(() => {
|
|
apiFetchMock.mockReset();
|
|
useLicenseMock.mockReset();
|
|
useExperimentalMock.mockReset();
|
|
useExperimentalMock.mockReturnValue({ experimental: true, experimentalReady: true });
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('useMeshDataPlane', () => {
|
|
it('does not fetch /mesh/status when the session is on the free tier', async () => {
|
|
useLicenseMock.mockReturnValue({ isPaid: false });
|
|
const { result } = renderHook(() => useMeshDataPlane());
|
|
await act(async () => { await Promise.resolve(); await Promise.resolve(); });
|
|
|
|
expect(apiFetchMock).not.toHaveBeenCalled();
|
|
expect(result.current.status).toBeNull();
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
it('fetches once on mount and surfaces the localDataPlane payload for a paid tier', async () => {
|
|
useLicenseMock.mockReturnValue({ isPaid: true });
|
|
apiFetchMock.mockResolvedValue(okJson({
|
|
localDataPlane: { ok: true, reason: null, lastChecked: 1000 },
|
|
}));
|
|
|
|
const { result } = renderHook(() => useMeshDataPlane());
|
|
await act(async () => { await Promise.resolve(); await Promise.resolve(); });
|
|
|
|
expect(apiFetchMock).toHaveBeenCalledWith('/mesh/status', expect.objectContaining({ localOnly: true }));
|
|
expect(result.current.status).toEqual({ ok: true, reason: null, lastChecked: 1000 });
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
it('keeps status null on a 403 response without raising an error', async () => {
|
|
useLicenseMock.mockReturnValue({ isPaid: true });
|
|
apiFetchMock.mockResolvedValue(statusJson(403, { error: 'forbidden' }));
|
|
|
|
const { result } = renderHook(() => useMeshDataPlane());
|
|
await act(async () => { await Promise.resolve(); await Promise.resolve(); });
|
|
|
|
expect(result.current.status).toBeNull();
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
it('falls back to null when the response omits localDataPlane', async () => {
|
|
useLicenseMock.mockReturnValue({ isPaid: true });
|
|
apiFetchMock.mockResolvedValue(okJson({ nodes: [] }));
|
|
|
|
const { result } = renderHook(() => useMeshDataPlane());
|
|
await act(async () => { await Promise.resolve(); await Promise.resolve(); });
|
|
|
|
expect(result.current.status).toBeNull();
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
it('does not fetch when paid but experimental discovery is off', async () => {
|
|
useLicenseMock.mockReturnValue({ isPaid: true });
|
|
useExperimentalMock.mockReturnValue({ experimental: false, experimentalReady: true });
|
|
const { result } = renderHook(() => useMeshDataPlane());
|
|
await act(async () => { await Promise.resolve(); await Promise.resolve(); });
|
|
|
|
expect(apiFetchMock).not.toHaveBeenCalled();
|
|
expect(result.current.status).toBeNull();
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
it('does not fetch while experimental metadata is still loading', async () => {
|
|
useLicenseMock.mockReturnValue({ isPaid: true });
|
|
useExperimentalMock.mockReturnValue({ experimental: false, experimentalReady: false });
|
|
const { result } = renderHook(() => useMeshDataPlane());
|
|
await act(async () => { await Promise.resolve(); await Promise.resolve(); });
|
|
|
|
expect(apiFetchMock).not.toHaveBeenCalled();
|
|
expect(result.current.status).toBeNull();
|
|
});
|
|
});
|