mirror of
https://github.com/hanzoai/extension.git
synced 2026-10-31 21:57:06 +00:00
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]>
101 lines
3.5 KiB
TypeScript
101 lines
3.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
pickBearer,
|
|
chatCompletionsURL,
|
|
modelsURL,
|
|
qboApiHost,
|
|
isQboEnv,
|
|
readServerConfig,
|
|
OAUTH_SCOPES,
|
|
HANZO_API_BASE_URL,
|
|
QBO_API_VERSION,
|
|
QBO_MINOR_VERSION,
|
|
INTUIT_AUTHORIZE_URL,
|
|
INTUIT_TOKEN_URL,
|
|
} from '../src/config';
|
|
|
|
describe('pickBearer', () => {
|
|
it('prefers the pasted API key over the oauth token', () => {
|
|
expect(pickBearer('sk-123', 'oauth-tok')).toBe('sk-123');
|
|
});
|
|
it('falls back to the oauth token when no key', () => {
|
|
expect(pickBearer('', 'oauth-tok')).toBe('oauth-tok');
|
|
expect(pickBearer(' ', 'oauth-tok')).toBe('oauth-tok');
|
|
});
|
|
it('is empty (anonymous) when neither is present', () => {
|
|
expect(pickBearer('', '')).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('gateway URLs', () => {
|
|
it('build /v1 endpoints on api.hanzo.ai with no /api/ prefix', () => {
|
|
expect(chatCompletionsURL()).toBe(`${HANZO_API_BASE_URL}/v1/chat/completions`);
|
|
expect(modelsURL()).toBe(`${HANZO_API_BASE_URL}/v1/models`);
|
|
expect(chatCompletionsURL()).not.toContain('/api/');
|
|
expect(HANZO_API_BASE_URL).toBe('https://api.hanzo.ai');
|
|
});
|
|
});
|
|
|
|
describe('Intuit + QBO hosts', () => {
|
|
it('pins the single Intuit OAuth authorize + token hosts', () => {
|
|
expect(INTUIT_AUTHORIZE_URL).toBe('https://appcenter.intuit.com/connect/oauth2');
|
|
expect(INTUIT_TOKEN_URL).toBe('https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer');
|
|
});
|
|
it('maps sandbox and production to the two Accounting API hosts', () => {
|
|
expect(qboApiHost('sandbox')).toBe('https://sandbox-quickbooks.api.intuit.com');
|
|
expect(qboApiHost('production')).toBe('https://quickbooks.api.intuit.com');
|
|
});
|
|
it('narrows only sandbox/production', () => {
|
|
expect(isQboEnv('sandbox')).toBe(true);
|
|
expect(isQboEnv('production')).toBe(true);
|
|
expect(isQboEnv('prod')).toBe(false);
|
|
expect(isQboEnv(undefined)).toBe(false);
|
|
});
|
|
it('requests the accounting + openid scopes', () => {
|
|
expect(OAUTH_SCOPES).toContain('com.intuit.quickbooks.accounting');
|
|
expect(OAUTH_SCOPES).toContain('openid');
|
|
});
|
|
it('uses Accounting API v3 with a pinned minorversion', () => {
|
|
expect(QBO_API_VERSION).toBe('v3');
|
|
expect(QBO_MINOR_VERSION).toMatch(/^\d+$/);
|
|
});
|
|
});
|
|
|
|
describe('readServerConfig', () => {
|
|
const base = {
|
|
INTUIT_CLIENT_ID: 'ik',
|
|
INTUIT_CLIENT_SECRET: 'sk',
|
|
INTUIT_REDIRECT_URI: 'https://quickbooks.hanzo.ai/oauth/callback',
|
|
};
|
|
|
|
it('reads a full config and defaults env=sandbox, port=8790', () => {
|
|
const cfg = readServerConfig(base);
|
|
expect(cfg.intuitClientId).toBe('ik');
|
|
expect(cfg.intuitClientSecret).toBe('sk');
|
|
expect(cfg.intuitRedirectUri).toBe('https://quickbooks.hanzo.ai/oauth/callback');
|
|
expect(cfg.qboEnv).toBe('sandbox');
|
|
expect(cfg.hanzoApiKey).toBe('');
|
|
expect(cfg.port).toBe(8790);
|
|
});
|
|
|
|
it('honors QBO_ENV=production, HANZO_API_KEY, and PORT', () => {
|
|
const cfg = readServerConfig({ ...base, QBO_ENV: 'production', HANZO_API_KEY: 'sk-x', PORT: '9100' });
|
|
expect(cfg.qboEnv).toBe('production');
|
|
expect(cfg.hanzoApiKey).toBe('sk-x');
|
|
expect(cfg.port).toBe(9100);
|
|
});
|
|
|
|
it('falls back to sandbox for an unknown QBO_ENV', () => {
|
|
expect(readServerConfig({ ...base, QBO_ENV: 'staging' }).qboEnv).toBe('sandbox');
|
|
});
|
|
|
|
it.each(['INTUIT_CLIENT_ID', 'INTUIT_CLIENT_SECRET', 'INTUIT_REDIRECT_URI'])(
|
|
'throws when %s is missing',
|
|
(key) => {
|
|
const env: Record<string, string | undefined> = { ...base };
|
|
delete env[key];
|
|
expect(() => readServerConfig(env)).toThrow(key);
|
|
},
|
|
);
|
|
});
|