Skip to content

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

Package listing

@kody/plaid

src/plaid-core.test.ts

131 lines · 5.0 KB · TypeScript
import assert from 'node:assert/strict'
import test from 'node:test'
import { createLinkToken } from './link.ts'
import {
	ACCESS_TOKEN_SETUP_URL,
	CLIENT_ID_SETUP_URL,
	DEFAULT_ACCESS_TOKEN_SECRET,
	DEFAULT_CLIENT_ID_SECRET,
	DEFAULT_SECRET_SECRET,
	PRODUCTION_API_BASE_URL,
	SANDBOX_API_BASE_URL,
	SANDBOX_API_HOST,
	SECRET_SETUP_URL,
	accessTokenForPreview,
	accessTokenSetupUrl,
	clientIdSetupUrl,
	getPlaidApiBaseUrl,
	mutationPreview,
	parseAction,
	requireConfirm,
	resolveAccessTokenSecretName,
	resolveClientIdSecretName,
	resolveEnvironment,
	resolveSecretSecretName,
	secretSetupUrl,
} from './plaid-core.ts'
import { createSandboxPublicToken } from './sandbox.ts'
import { exchangePublicToken } from './item.ts'

test('resolve secret names default and suffix accounts', () => {
	assert.equal(resolveClientIdSecretName(), DEFAULT_CLIENT_ID_SECRET)
	assert.equal(resolveClientIdSecretName({ account: 'default' }), DEFAULT_CLIENT_ID_SECRET)
	assert.equal(resolveClientIdSecretName({ account: 'work' }), 'plaidClientId-work')
	assert.equal(resolveSecretSecretName({ account: 'work' }), 'plaidSecret-work')
	assert.equal(resolveAccessTokenSecretName({ account: 'work' }), 'plaidAccessToken-work')
	assert.equal(resolveClientIdSecretName({ clientIdSecret: 'plaidClientId-live' }), 'plaidClientId-live')
})

test('resolve secret names reject Kent-style aliases and invalid labels', () => {
	assert.throws(() => resolveClientIdSecretName({ clientIdSecret: 'PLAID_CLIENT_ID' }), /plaidClientId/)
	assert.throws(() => resolveSecretSecretName({ secretSecret: 'plaid_secret' }), /plaidSecret/)
	assert.throws(() => resolveAccessTokenSecretName({ accessTokenSecret: 'kentPlaidToken' }), /plaidAccessToken/)
	assert.throws(() => resolveClientIdSecretName({ account: 'youtube-brand!' }), /short label/)
})

test('setup URLs stay prefilled for the hosted secrets page', () => {
	assert.equal(clientIdSetupUrl(), CLIENT_ID_SETUP_URL)
	assert.equal(secretSetupUrl(), SECRET_SETUP_URL)
	assert.equal(accessTokenSetupUrl(), ACCESS_TOKEN_SETUP_URL)
	assert.match(CLIENT_ID_SETUP_URL, /name=plaidClientId/)
	assert.match(SECRET_SETUP_URL, /name=plaidSecret/)
	assert.match(CLIENT_ID_SETUP_URL, /allowedHosts=sandbox\.plaid\.com,production\.plaid\.com/)
	assert.match(SECRET_SETUP_URL, /PLAID_SECRET/)
	assert.match(accessTokenSetupUrl('plaidAccessToken-work'), /name=plaidAccessToken-work/)
	assert.match(ACCESS_TOKEN_SETUP_URL, /Not%20a%20bank%20password/)
})

test('environment defaults to sandbox', () => {
	assert.equal(resolveEnvironment(), 'sandbox')
	assert.equal(getPlaidApiBaseUrl(), SANDBOX_API_BASE_URL)
	assert.equal(getPlaidApiBaseUrl({ environment: 'production' }), PRODUCTION_API_BASE_URL)
	assert.throws(() => resolveEnvironment({ environment: 'development' as 'sandbox' }), /sandbox/)
})

test('access token preview never emits a resolvable secret placeholder', () => {
	assert.equal(accessTokenForPreview(), 'secret:plaidAccessToken')
	assert.equal(accessTokenForPreview({ accessToken: 'access-sandbox-example' }), '<access_token>')
	assert.doesNotMatch(accessTokenForPreview(), /\{\{secret:/)
})

test('requireConfirm blocks live mutations', () => {
	assert.throws(() => requireConfirm({}, 'create link token'), /confirm: true/)
	requireConfirm({ confirm: true }, 'create link token')
})

test('mutationPreview defaults to dry-run without confirm', () => {
	const preview = mutationPreview(
		{},
		{
			action: 'create link token',
			method: 'POST',
			path: '/link/token/create',
			body: { client_name: 'Kody' },
		},
	)
	assert.deepEqual(preview, {
		dryRun: true,
		action: 'create link token',
		method: 'POST',
		path: '/link/token/create',
		environment: 'sandbox',
		host: SANDBOX_API_HOST,
		body: { client_name: 'Kody' },
	})
})

test('mutationPreview confirm without dryRun is live (returns null)', () => {
	const preview = mutationPreview(
		{ confirm: true },
		{ action: 'create link token', method: 'POST', path: '/link/token/create' },
	)
	assert.equal(preview, null)
})

test('parseAction defaults and rejects unknown actions', () => {
	assert.equal(parseAction(undefined, ['smoke-test', 'get-item'], 'smoke-test', 'Plaid'), 'smoke-test')
	assert.throws(() => parseAction('refund', ['smoke-test'], 'smoke-test', 'Plaid'), /Unknown Plaid action/)
})

test('createLinkToken defaults to a dry-run preview', async () => {
	const preview = await createLinkToken({ clientUserId: 'user-1' })
	assert.equal('dryRun' in preview && preview.dryRun, true)
	if ('dryRun' in preview) {
		assert.equal(preview.path, '/link/token/create')
		assert.equal(preview.body?.client_name, 'Kody')
	}
})

test('createSandboxPublicToken defaults to dry-run and rejects production', async () => {
	const preview = await createSandboxPublicToken()
	assert.equal('dryRun' in preview && preview.dryRun, true)
	await assert.rejects(
		() => createSandboxPublicToken({ environment: 'production', confirm: true }),
		/Sandbox-only/,
	)
})

test('exchangePublicToken dry-run does not require a public token', async () => {
	const preview = await exchangePublicToken()
	assert.equal('dryRun' in preview && preview.dryRun, true)
})