Skip to content

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

Package listing

@kody/trello

src/trello.test.ts

136 lines · 5.3 KB · TypeScript
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
import {
	API_KEY_SETUP_URL,
	DEFAULT_API_KEY_SECRET,
	DEFAULT_TOKEN_SECRET,
	OAUTH_CONNECT_URL,
	TOKEN_SETUP_URL,
	TRELLO_CALLBACK_URL,
	apiKeySetupUrl,
	mutationPreview,
	normalizeTrelloPath,
	oauthConnectUrl,
	resolveApiKeySecretName,
	resolveIntegrationName,
	resolveTokenSecretName,
	tokenSetupUrl,
	trelloAuthorizeUrl,
	trelloUrl,
} from './setup.ts'
import { boardSummary, cardSummary, commentSummary, listSummary } from './models.ts'

const here = dirname(fileURLToPath(import.meta.url))
const root = join(here, '..')

test('secret and integration names default and suffix accounts', () => {
	assert.equal(resolveIntegrationName(), 'trello')
	assert.equal(resolveTokenSecretName(), DEFAULT_TOKEN_SECRET)
	assert.equal(resolveApiKeySecretName(), DEFAULT_API_KEY_SECRET)
	assert.equal(resolveIntegrationName({ account: 'default' }), 'trello')
	assert.equal(resolveIntegrationName({ account: 'work' }), 'trello-work')
	assert.equal(resolveTokenSecretName({ account: 'work' }), 'trelloToken-work')
	assert.equal(resolveApiKeySecretName({ account: 'work' }), 'trelloApiKey-work')
	assert.equal(resolveIntegrationName({ integrationName: 'trello-brand' }), 'trello-brand')
	assert.equal(resolveTokenSecretName({ secretName: 'trelloToken-brand' }), 'trelloToken-brand')
	assert.equal(
		resolveApiKeySecretName({ apiKeySecretName: 'trelloApiKey-brand' }),
		'trelloApiKey-brand',
	)
})

test('setup URLs are prefilled for OAuth and secrets', () => {
	assert.match(OAUTH_CONNECT_URL, /provider=trello/)
	assert.match(OAUTH_CONNECT_URL, /apiBaseUrl=https%3A%2F%2Fapi\.trello\.com%2F1/)
	assert.match(OAUTH_CONNECT_URL, /allowedHosts=api\.trello\.com/)
	assert.equal(TRELLO_CALLBACK_URL, 'https://kody.codes/connect/oauth')
	assert.match(API_KEY_SETUP_URL, /name=trelloApiKey/)
	assert.match(API_KEY_SETUP_URL, /allowedHosts=api\.trello\.com/)
	assert.match(API_KEY_SETUP_URL, /scope=user/)
	assert.match(TOKEN_SETUP_URL, /name=trelloToken/)
	assert.match(TOKEN_SETUP_URL, /allowedHosts=api\.trello\.com/)
	assert.match(oauthConnectUrl('trello-work'), /provider=trello-work/)
	assert.match(apiKeySetupUrl('trelloApiKey-work'), /name=trelloApiKey-work/)
	assert.match(tokenSetupUrl('trelloToken-work'), /name=trelloToken-work/)
	assert.match(trelloAuthorizeUrl(), /trello\.com\/1\/authorize/)
	assert.match(trelloAuthorizeUrl(), /scope=read%2Cwrite/)
	assert.match(trelloAuthorizeUrl(), /key=YOUR_API_KEY/)
})

test('mutationPreview dryRun skips confirm', () => {
	const preview = mutationPreview(
		{ dryRun: true },
		{
			method: 'POST',
			path: '/cards',
			body: { name: 'Write docs', idList: 'list-from-caller' },
		},
	)
	assert.deepEqual(preview, {
		dryRun: true,
		method: 'POST',
		path: '/cards',
		body: { name: 'Write docs', idList: 'list-from-caller' },
	})
})

test('mutationPreview requires confirm for live writes', () => {
	assert.throws(
		() =>
			mutationPreview(
				{},
				{ method: 'POST', path: '/boards', body: { name: 'Planning' } },
			),
		/confirm: true/,
	)
	assert.equal(
		mutationPreview(
			{ confirm: true },
			{ method: 'POST', path: '/boards', body: { name: 'Planning' } },
		),
		null,
	)
})

test('trelloUrl stays on api.trello.com/1', () => {
	assert.equal(normalizeTrelloPath('/members/me'), '/1/members/me')
	assert.equal(normalizeTrelloPath('/1/boards'), '/1/boards')
	assert.match(trelloUrl('/members/me'), /^https:\/\/api\.trello\.com\/1\/members\/me$/)
	assert.match(trelloUrl('/boards', { filter: 'open' }), /filter=open/)
	assert.throws(() => normalizeTrelloPath('https://example.com/1/members/me'), /api\.trello\.com/)
})

test('summaries stay generic and require ids from the payload', () => {
	assert.equal(boardSummary({ name: 'Nope' }), null)
	assert.deepEqual(boardSummary({ id: 'board-from-caller', name: 'Planning' })?.name, 'Planning')
	assert.equal(listSummary({ name: 'Todo' }), null)
	assert.equal(listSummary({ id: 'list-from-caller', name: 'Todo' })?.id, 'list-from-caller')
	assert.equal(cardSummary({ name: 'Task' }), null)
	assert.equal(cardSummary({ id: 'card-from-caller', name: 'Task' })?.name, 'Task')
	assert.equal(commentSummary({ data: { text: 'Hi' } }), null)
})

test('package is public MIT official trello with disabled job', () => {
	const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
	assert.equal(manifest.name, '@kody/trello')
	assert.equal(manifest.license, 'MIT')
	assert.equal(manifest.private, false)
	assert.equal(manifest.kody.id, 'trello')
	assert.equal(manifest.kody.jobs['account-health'].enabled, false)
	const readme = readFileSync(join(root, 'README.md'), 'utf8')
	assert.match(readme, /## Intent/)
	assert.match(readme, /https:\/\/kody\.codes\/@kody\/trello/)
	assert.match(readme, /\/account\/secrets\/new\?name=trelloApiKey/)
	assert.match(readme, /\/account\/secrets\/new\?name=trelloToken/)
	assert.match(readme, /\/connect\/oauth\?provider=trello/)
	assert.doesNotMatch(readme, /\/community\/[0-9a-f-]{36}/)
	assert.doesNotMatch(readme, /hello@kentcdodds\.com/)
	assert.doesNotMatch(readme, /betterWithKent/)
	assert.doesNotMatch(readme, /[0-9a-f]{24}/)
	const icon = readFileSync(join(root, 'community-icon.svg'), 'utf8')
	assert.match(icon, /#0052cc/i)
	assert.match(icon, /Official Trello logomark/)
})