Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/hubspot

src/smoke-test.ts

99 lines · 3.4 KB · TypeScript
import { accounts } from './accounts.ts'
import { getAccount } from './account.ts'
import { inferOperationFromPath, oauthConnectUrl, scopeForOperation } from './setup.ts'
import { normalizeHubSpotPath } from './client.ts'
import { resolveObjectSpec } from './models.ts'
import type { HubSpotAuthInput } from './types.ts'
import { optionalString, requireRecord } from './types.ts'
import { requireAuthMode } from './auth.ts'

export type SmokeTestInput = HubSpotAuthInput & {
	/** When true, skip the live portal query even if credentials exist. */
	setupOnly?: boolean
}

/**
 * Verify HubSpot auth without returning portal PII. Without credentials this
 * still returns setup URLs so the package is forkable before anyone connects.
 * @example
 * import smokeTest from 'kody:@kody/hubspot/smoke-test'
 * const result = await smokeTest()
 */
export async function smokeTest(params: SmokeTestInput = {}) {
	const connectUrl = oauthConnectUrl('hubspot')
	const selfCheck = {
		ok: true as const,
		pathKeepsCrmPrefix: normalizeHubSpotPath('/crm/v3/objects/contacts') === '/crm/v3/objects/contacts',
		contactWriteScope: scopeForOperation('contacts.write') === 'crm.objects.contacts.write',
		ticketWriteFromPath:
			inferOperationFromPath('PATCH', '/crm/v3/objects/tickets/1') === 'tickets.write',
		customReadFromPath:
			inferOperationFromPath('GET', '/crm/v3/objects/p_listings') === 'custom.read',
		searchIsCustomRead:
			inferOperationFromPath('POST', '/crm/v3/objects/contacts/search') === 'contacts.read',
		dealSpec: resolveObjectSpec('deals').writeOperation === 'deals.write',
		connectUrlUsesHubSpotHost: connectUrl.includes('app.hubspot.com'),
		connectUrlHasContactsScope: connectUrl.includes('crm.objects.contacts.read'),
	}
	const info = await accounts(params)
	const canLive = Boolean(info.preferredAuth) && params.setupOnly !== true

	if (!canLive) {
		return {
			ok: true,
			live: false,
			selfCheck,
			authMode: info.preferredAuth,
			integrationName: info.integrationName,
			secretName: info.secretName,
			oauthConnected: info.oauthConnected,
			privateAppSaved: info.privateAppSaved,
			hasPortalId: false,
			setup: {
				connectUrl: info.connectUrl,
				privateAppUrl: info.privateAppUrl,
				callbackUrl: info.callbackUrl,
				requiredHosts: info.requiredHosts,
			},
		}
	}

	const account = await getAccount(params)
	return {
		ok: true,
		live: true,
		selfCheck,
		authMode: info.preferredAuth,
		integrationName: info.integrationName,
		secretName: info.secretName,
		oauthConnected: info.oauthConnected,
		privateAppSaved: info.privateAppSaved,
		hasPortalId: account.hasPortalId,
		setup: {
			connectUrl: info.connectUrl,
			privateAppUrl: info.privateAppUrl,
			callbackUrl: info.callbackUrl,
			requiredHosts: info.requiredHosts,
		},
	}
}

/**
 * Verify HubSpot auth without returning portal PII.
 * @example
 * import smokeTest from 'kody:@kody/hubspot/smoke-test'
 * const result = await smokeTest({ setupOnly: true })
 */
export default async function smokeTestEntrypoint(
	params: SmokeTestInput & Record<string, unknown> = {},
) {
	const input = requireRecord(params, 'smoke-test')
	return smokeTest({
		integrationName: optionalString(input.integrationName, 'integrationName'),
		integration: optionalString(input.integration, 'integration'),
		account: optionalString(input.account, 'account'),
		secretName: optionalString(input.secretName, 'secretName'),
		auth: requireAuthMode(input.auth),
		setupOnly: input.setupOnly === true,
	})
}