diff --git a/apps/api/package.json b/apps/api/package.json index aa000bc..1b47c1e 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -16,7 +16,8 @@ "@azure/keyvault-secrets": "^4.11.2", "@azure/storage-blob": "^12.33.0", "@singleton-sd/post-kit-email": "workspace:*", - "@singleton-sd/post-kit-types": "workspace:^" + "@singleton-sd/post-kit-types": "workspace:^", + "handlebars": "^4.7.8" }, "devDependencies": { "@types/node": "^22.10.2", diff --git a/apps/api/src/functions/send.spec.ts b/apps/api/src/functions/send.spec.ts new file mode 100644 index 0000000..7788d29 --- /dev/null +++ b/apps/api/src/functions/send.spec.ts @@ -0,0 +1,316 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import type { HttpRequest, InvocationContext } from '@azure/functions'; +import type { + EmailProvider, + EmailSendRequest, + EmailSendResult, +} from '@singleton-sd/post-kit-email'; +import { + PostKitErrorCode, + TEMPLATE_SCHEMA_VERSION, + type CompiledTemplate, + type TenantContext, +} from '@singleton-sd/post-kit-types'; +import { TenantResolverError, type TenantResolver } from '../tenant'; +import { TemplateStoreError, type TemplateStore } from '../templates'; +import { createSendHandler } from './send'; + +const TENANT: TenantContext = { tenantId: 'inkads', environment: 'development' }; + +const COMPILED: CompiledTemplate = { + templateHtml: '
Hello {{name}}
', + metadata: { + key: 'marketing.contact-us', + name: 'Contact Us', + subject: 'Hi {{name}}', + variables: ['name'], + schemaVersion: TEMPLATE_SCHEMA_VERSION, + }, + manifest: { + key: 'marketing.contact-us', + schemaVersion: TEMPLATE_SCHEMA_VERSION, + compiledAt: '2026-01-01T00:00:00.000Z', + sourceCommit: '', + variables: ['name'], + contentHash: 'abc', + }, +}; + +function fakeRequest(options: { headers?: RecordHello Ada
'); + }); + + it('HTML-escapes variables in the body', async () => { + const sent: EmailSendRequest[] = []; + const handler = createSendHandler({ + tenantResolver: fakeResolver(), + templateStore: fakeStore(COMPILED), + emailProvider: fakeProvider(sent), + fromAddress: () => 'noreply@example.com', + }); + + await handler( + fakeRequest({ + json: { + template: 'marketing.contact-us', + to: 'user@example.com', + variables: { name: '' }, + }, + }), + fakeContext(), + ); + + assert.ok(sent[0]?.html?.includes('<script>')); + assert.ok(!sent[0]?.html?.includes('