Files
zooqueenandHanzo Dev f717dd89ad keys: the key a user pastes is sk-, not the retired hk-
28 apps told the user to paste an `hk-` key — input placeholders, settings
help text, READMEs, the error thrown when the field is empty — and five of
them validated that paste against /^hk-/ before sending it as a bearer.
Nothing mints that prefix any more: cloud accepts pk- and sk-, IAM mints
sk-. So the guard admitted a credential that authenticates nothing, and the
panel failed later at the gateway with nothing on screen to explain it.

The three leak detectors keep hk- and gain sk-. Salesforce's source scan
and the two Zendesk tests exist to catch a key committed to source or
echoed back through a request; retired keys are still in circulation and
leak exactly as badly, while a detector that only knew hk- would have gone
blind to every key minted from here on.

`sk-` used to be the example of the foreign key the guard screened out — an
OpenAI key. It cannot mean that now, and shape alone cannot tell one
vendor's sk- from another's, so those comments say what the check really
proves and point at the /v1/models call that proves the rest. The three
tests that pinned "sk- is rejected" now pin "hk- is rejected", which is the
invariant that survived.

Verified: 2029 tests pass across the 28 packages; typecheck clean on 25 of
them (hubspot, jupyter and teams fail identically at HEAD on JSX and ESM
type conflicts in files this does not touch); salesforce validate-source
green.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-13 18:07:16 -07:00

95 lines
4.4 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
chatRequestBody,
extractContent,
parseModels,
createAiClient,
} from '../src/client';
describe('chatRequestBody', () => {
it('always sets stream:false and includes model+messages', () => {
const body = chatRequestBody({ model: 'zen5', messages: [{ role: 'user', content: 'hi' }] });
expect(body).toMatchObject({ model: 'zen5', stream: false });
expect(body.messages).toHaveLength(1);
expect('temperature' in body).toBe(false);
expect('max_tokens' in body).toBe(false);
});
it('includes temperature/max_tokens only when provided', () => {
const body = chatRequestBody({ model: 'zen5', messages: [], temperature: 0.2, max_tokens: 500 });
expect(body.temperature).toBe(0.2);
expect(body.max_tokens).toBe(500);
});
});
describe('extractContent', () => {
it('reads choices[].message.content', () => {
expect(extractContent({ choices: [{ message: { role: 'assistant', content: 'hello' } }] })).toBe('hello');
});
it('falls back to choices[].text and top-level content', () => {
expect(extractContent({ choices: [{ text: 'legacy' }] })).toBe('legacy');
expect(extractContent({ content: 'top' })).toBe('top');
});
it('throws the gateway error message', () => {
expect(() => extractContent({ error: { message: 'rate limited' } })).toThrow(/rate limited/);
expect(() => extractContent({ error: 'bad key' })).toThrow(/bad key/);
});
it('throws on empty content', () => {
expect(() => extractContent({ choices: [{ message: { role: 'assistant', content: '' } }] })).toThrow(/no content/);
});
});
describe('parseModels', () => {
it('reads data[] of objects, models[], or a bare array of strings', () => {
expect(parseModels({ data: [{ id: 'zen5' }, { id: 'zen-coder' }] }).map((m) => m.id)).toEqual(['zen5', 'zen-coder']);
expect(parseModels({ models: [{ id: 'a' }] }).map((m) => m.id)).toEqual(['a']);
expect(parseModels(['x', 'y']).map((m) => m.id)).toEqual(['x', 'y']);
});
it('drops entries without an id', () => {
expect(parseModels({ data: [{ id: '' }, { foo: 1 }, { id: 'ok' }] }).map((m) => m.id)).toEqual(['ok']);
});
});
describe('createAiClient (injected fetch — no network)', () => {
it('POSTs /v1/chat/completions with the bearer and returns the completion', async () => {
let seen: { url: string; init: RequestInit } | null = null;
const fakeFetch = (async (url: any, init: any) => {
seen = { url: String(url), init };
return new Response(JSON.stringify({ choices: [{ message: { role: 'assistant', content: 'ok' } }] }), { status: 200 });
}) as unknown as typeof fetch;
const ai = createAiClient({ token: 'sk-1', baseURL: 'https://api.hanzo.ai', fetch: fakeFetch });
const res = await ai.chat.completions.create({ model: 'zen5', messages: [{ role: 'user', content: 'hi' }] });
expect(extractContent(res)).toBe('ok');
expect(seen!.url).toBe('https://api.hanzo.ai/v1/chat/completions');
expect((seen!.init.headers as any).Authorization).toBe('Bearer sk-1');
const sent = JSON.parse(seen!.init.body as string);
expect(sent).toMatchObject({ model: 'zen5', stream: false });
});
it('omits Authorization when the token is empty (anonymous)', async () => {
let headers: any;
const fakeFetch = (async (_url: any, init: any) => {
headers = init.headers;
return new Response(JSON.stringify({ choices: [{ message: { content: 'x' } }] }), { status: 200 });
}) as unknown as typeof fetch;
const ai = createAiClient({ fetch: fakeFetch });
await ai.chat.completions.create({ model: 'zen5', messages: [] });
expect('Authorization' in headers).toBe(false);
});
it('surfaces a non-2xx gateway error', async () => {
const fakeFetch = (async () => new Response(JSON.stringify({ error: { message: 'nope' } }), { status: 401 })) as unknown as typeof fetch;
const ai = createAiClient({ fetch: fakeFetch });
await expect(ai.chat.completions.create({ model: 'zen5', messages: [] })).rejects.toThrow(/nope/);
});
it('lists models from /v1/models', async () => {
const fakeFetch = (async (url: any) => {
expect(String(url)).toBe('https://api.hanzo.ai/v1/models');
return new Response(JSON.stringify({ data: [{ id: 'zen5' }] }), { status: 200 });
}) as unknown as typeof fetch;
const ai = createAiClient({ fetch: fakeFetch });
expect((await ai.models.list()).map((m) => m.id)).toEqual(['zen5']);
});
});