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

82 lines
2.6 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { buildMessages, extractContent, complete } from '../src/hanzo.js';
import type { AiClient, ChatCompletion } from '@hanzo/ai';
describe('buildMessages', () => {
it('produces a system+user pair in order', () => {
const msgs = buildMessages('SYS', 'USER');
expect(msgs).toEqual([
{ role: 'system', content: 'SYS' },
{ role: 'user', content: 'USER' },
]);
});
});
describe('extractContent', () => {
it('reads a string content body', () => {
expect(extractContent({ choices: [{ message: { content: 'hi' } }] })).toBe('hi');
});
it('joins text content parts', () => {
const res = {
choices: [{ message: { content: [{ type: 'text', text: 'a' }, { type: 'text', text: 'b' }] } }],
};
expect(extractContent(res)).toBe('ab');
});
it('throws on empty content', () => {
expect(() => extractContent({ choices: [{ message: { content: '' } }] })).toThrow(/no content/);
expect(() => extractContent({ choices: [] })).toThrow(/no content/);
});
});
describe('complete (request shaping via mock client)', () => {
it('shapes model + messages, non-stream, and returns assistant text', async () => {
const seen: unknown[] = [];
const fake: AiClient = {
chat: {
completions: {
async create(params: unknown) {
seen.push(params);
return {
id: 'x',
object: 'chat.completion',
created: 0,
model: 'zen5',
choices: [{ index: 0, message: { role: 'assistant', content: 'REVIEW' }, finish_reason: 'stop' }],
} as ChatCompletion;
},
},
},
} as unknown as AiClient;
const out = await complete({
system: 'SYS',
user: 'USER',
model: 'zen5',
token: 'sk-abc',
baseURL: 'https://api.hanzo.ai',
client: fake,
});
expect(out).toBe('REVIEW');
expect(seen).toHaveLength(1);
const params = seen[0] as { model: string; messages: unknown[]; stream: boolean };
expect(params.model).toBe('zen5');
expect(params.stream).toBe(false);
expect(params.messages).toEqual([
{ role: 'system', content: 'SYS' },
{ role: 'user', content: 'USER' },
]);
});
it('propagates a gateway error', async () => {
const fake = {
chat: { completions: { async create() { throw new Error('HTTP 500'); } } },
} as unknown as AiClient;
await expect(
complete({ system: 's', user: 'u', model: 'm', token: '', baseURL: 'x', client: fake }),
).rejects.toThrow(/HTTP 500/);
});
});