mirror of
https://github.com/hanzoai/extension.git
synced 2026-10-31 21:57:06 +00:00
A HubSpot public app on the developer-projects platform: a CRM card UI extension (crm.record.tab, on contacts/companies/deals) backed by two serverless functions. Summarize a record, draft an email, suggest next steps, or ask — grounded in the record + its associated records — and write the result back as a Note or Task. Built on the published @hanzo/ai@^0.2.0 headless client (createAiClient, /v1 chat.completions + models.list) + @hanzo/iam. The Hanzo API key (HANZO_API_KEY secret) and the per-portal OAuth token live only in the serverless backend; the browser never sees them. Pure, unit-tested core (config, hanzo call + context windowing + the four actions, HubSpot CRM v3 request shaping/parsing, OAuth token exchange, record→context assembly, Note/Task write-back payloads). 75 vitest tests green, tsc --noEmit clean, dual ESM+CJS build so the CJS serverless runtime can require it.
49 lines
1.4 KiB
TypeScript
49 lines
1.4 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
HANZO_API_BASE_URL,
|
|
DEFAULT_MODEL,
|
|
crmApiRoot,
|
|
hubspotAuthorizeURL,
|
|
hubspotTokenURL,
|
|
isObjectType,
|
|
SUPPORTED_OBJECT_TYPES,
|
|
RECORD_CHAR_BUDGET,
|
|
} from '../src/config.js';
|
|
|
|
describe('Hanzo gateway config', () => {
|
|
it('points at api.hanzo.ai with no /api/ prefix', () => {
|
|
expect(HANZO_API_BASE_URL).toBe('https://api.hanzo.ai');
|
|
expect(HANZO_API_BASE_URL).not.toContain('/api/');
|
|
});
|
|
it('defaults to a zen model', () => {
|
|
expect(DEFAULT_MODEL).toMatch(/^zen/);
|
|
});
|
|
});
|
|
|
|
describe('HubSpot endpoints', () => {
|
|
it('crmApiRoot is /crm/v3 on the api host', () => {
|
|
expect(crmApiRoot()).toBe('https://api.hubapi.com/crm/v3');
|
|
});
|
|
it('authorize is on the app host, token is on the api host', () => {
|
|
expect(hubspotAuthorizeURL()).toBe('https://app.hubspot.com/oauth/authorize');
|
|
expect(hubspotTokenURL()).toBe('https://api.hubapi.com/oauth/v1/token');
|
|
});
|
|
});
|
|
|
|
describe('isObjectType', () => {
|
|
it('accepts the three supported types', () => {
|
|
for (const t of SUPPORTED_OBJECT_TYPES) expect(isObjectType(t)).toBe(true);
|
|
});
|
|
it('rejects anything else', () => {
|
|
expect(isObjectType('tickets')).toBe(false);
|
|
expect(isObjectType(undefined)).toBe(false);
|
|
expect(isObjectType('')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('RECORD_CHAR_BUDGET', () => {
|
|
it('is a sane positive cap', () => {
|
|
expect(RECORD_CHAR_BUDGET).toBeGreaterThan(1000);
|
|
});
|
|
});
|