learning_ai_common_plat/packages/kill-switch-client/src/index.test.ts
saravanakumardb1 7e08cce95f fix(kill-switch-client): point at /settings/kill-switch (the real endpoint)
The client was calling GET ${baseUrl}/flags/kill-switch which does
not exist on platform-service. The actual kill-switch endpoint lives
under /settings/kill-switch in the settings module (public, no auth
required). The bug was silently masked by the client's fail-open
behavior on non-OK responses, but it produced a 404 on every page
load for every consumer (NoteLett, MindLyst, ChronoMind, FlowMonk,
NomGap, PeakPulse, JarvisJr, LysnrAI, ActionTrail, EffoRise, Local
Memory GPT).

Discovery: running the deployed NoteLett docker stack against the
sibling platform-service, every page load triggered:
  GET http://localhost:4003/api/flags/kill-switch?platform=web → 404
Confirmed by curl-ing both endpoints directly:
  /api/flags/kill-switch        → {"message":"Route GET:/api/flags/kill-switch not found"}
  /api/settings/kill-switch     → {"enabled":true,"disabled":false,"message":""}

Also adds the productId as a query param. The server route accepts
productId from the query string OR an x-product-id header — sending
both is harmless and improves debuggability when grepping logs.

Updated JSDoc and the corresponding test assertion. Test count
unchanged (6 passed).

Verified:
  pnpm --filter @bytelyst/kill-switch-client test → 6/6 passed
  pnpm --filter @bytelyst/kill-switch-client build → ok
  curl /api/settings/kill-switch?productId=notelett → 200 with payload
2026-05-23 10:17:42 -07:00

103 lines
2.7 KiB
TypeScript

import { describe, it, expect, vi, afterEach } from 'vitest';
import { createKillSwitchClient } from './index.js';
describe('createKillSwitchClient', () => {
const baseConfig = {
baseUrl: 'http://localhost:4003/api',
productId: 'testapp',
};
afterEach(() => {
vi.restoreAllMocks();
});
it('should return disabled=false when app is not disabled', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ disabled: false, message: null }),
})
);
const ks = createKillSwitchClient(baseConfig);
const result = await ks.check();
expect(result.disabled).toBe(false);
expect(result.message).toBeNull();
});
it('should return disabled=true with message when app is disabled', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ disabled: true, message: 'Maintenance in progress' }),
})
);
const ks = createKillSwitchClient(baseConfig);
const result = await ks.check();
expect(result.disabled).toBe(true);
expect(result.message).toBe('Maintenance in progress');
});
it('should fail-open on network error', async () => {
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('network')));
const ks = createKillSwitchClient(baseConfig);
const result = await ks.check();
expect(result.disabled).toBe(false);
expect(result.message).toBeNull();
});
it('should fail-open on non-OK response', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
ok: false,
status: 500,
})
);
const ks = createKillSwitchClient(baseConfig);
const result = await ks.check();
expect(result.disabled).toBe(false);
});
it('should send correct product-id header', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ disabled: false }),
});
vi.stubGlobal('fetch', fetchMock);
const ks = createKillSwitchClient(baseConfig);
await ks.check();
expect(fetchMock).toHaveBeenCalledWith(
expect.stringContaining('/settings/kill-switch'),
expect.objectContaining({
headers: expect.objectContaining({ 'x-product-id': 'testapp' }),
})
);
});
it('should include platform in query string', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ disabled: false }),
});
vi.stubGlobal('fetch', fetchMock);
const ks = createKillSwitchClient({ ...baseConfig, platform: 'ios' });
await ks.check();
const url = fetchMock.mock.calls[0][0] as string;
expect(url).toContain('platform=ios');
});
});