rendered html
'), + }), +); + +const baseSettings: WorkflowActionSettings = { + outputSchema: {}, + errorHandlingOptions: { + retryOnFailure: { value: false }, + continueOnFailure: { value: false }, + }, + input: {}, +}; + +const buildDraftEmailStep = (input: RecordHello
', +}; + +const successOutput: ToolOutput = { + success: true, + message: 'Email sent successfully to Alice', + result: { + recipients: ['alice@example.com', 'bob@example.com'], + ccRecipients: [], + bccRecipients: [], + subject: 'Welcome', + connectedAccountId: 'account-1', + attachmentCount: 0, + }, +}; + +describe('buildEmailStepLog', () => { + it('builds a SUCCESS email log preferring parsed recipients from the tool output', () => { + const stepLog = buildEmailStepLog({ + mode: 'SEND', + input: baseInput, + output: successOutput, + durationMs: 120, + }); + + if (stepLog.details.type !== 'EMAIL') { + throw new Error('Expected EMAIL details'); + } + + expect(stepLog.details.mode).toBe('SEND'); + expect(stepLog.details.status).toBe('SUCCESS'); + expect(stepLog.details.recipients.to).toEqual([ + 'alice@example.com', + 'bob@example.com', + ]); + expect(stepLog.details.recipients.cc).toBeUndefined(); + expect(stepLog.details.subject).toBe('Welcome'); + expect(stepLog.details.attachmentCount).toBe(0); + expect(stepLog.details.durationMs).toBe(120); + }); + + it('falls back to splitting the comma-separated input when the tool output has no parsed recipients', () => { + const stepLog = buildEmailStepLog({ + mode: 'DRAFT', + input: { + ...baseInput, + recipients: { + to: 'alice@example.com,bob@example.com ; carol@example.com', + cc: 'dan@example.com', + }, + }, + output: { + success: false, + message: 'Failed to create draft', + error: 'Connected account expired', + }, + durationMs: 5, + }); + + if (stepLog.details.type !== 'EMAIL') { + throw new Error('Expected EMAIL details'); + } + + expect(stepLog.details.mode).toBe('DRAFT'); + expect(stepLog.details.status).toBe('ERROR'); + expect(stepLog.details.recipients.to).toEqual([ + 'alice@example.com', + 'bob@example.com', + 'carol@example.com', + ]); + expect(stepLog.details.recipients.cc).toEqual(['dan@example.com']); + expect(stepLog.details.error).toBe('Connected account expired'); + }); + + it('truncates oversized body previews and reports original byte size', () => { + const longBody = `${'x'.repeat(20_000)}
`; + + const stepLog = buildEmailStepLog({ + mode: 'SEND', + input: { ...baseInput, body: longBody }, + output: successOutput, + durationMs: 10, + }); + + if (stepLog.details.type !== 'EMAIL') { + throw new Error('Expected EMAIL details'); + } + + expect(stepLog.details.bodyTruncated).toBe(true); + expect(stepLog.details.bodyPreview).toContain('truncated'); + expect(stepLog.details.bodyBytes).toBeGreaterThan(20_000); + }); + + it('prefers the sanitized HTML body from the tool output over the raw input body', () => { + const stepLog = buildEmailStepLog({ + mode: 'SEND', + input: { + ...baseInput, + body: 'Hello
', + }, + output: { + ...successOutput, + result: { + ...(successOutput.result as object), + sanitizedHtmlBody: 'Hello
', + plainTextBody: 'Hello', + }, + }, + durationMs: 10, + }); + + if (stepLog.details.type !== 'EMAIL') { + throw new Error('Expected EMAIL details'); + } + + expect(stepLog.details.bodyPreview).toBe('Hello
'); + expect(stepLog.details.bodyPreview).not.toContain('