Files
accounted/lib/support/__tests__/attachments.test.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

95 lines
3.7 KiB
TypeScript

import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { describe, it, expect } from 'vitest'
import {
SUPPORT_ATTACHMENT_ACCEPT,
SUPPORT_MAX_ATTACHMENTS,
SUPPORT_MAX_ATTACHMENT_TOTAL_BYTES,
SUPPORT_MAX_ATTACHMENT_TOTAL_MB,
isSupportedAttachmentType,
sanitizeAttachmentFilename,
supportAttachmentFilename,
} from '@/lib/support/attachments'
import { HOSTED_REQUEST_BODY_LIMIT_BYTES } from '@/lib/documents/upload-size'
describe('support attachments', () => {
it('accepts the types a support reader can open', () => {
expect(isSupportedAttachmentType('image/png')).toBe(true)
expect(isSupportedAttachmentType('IMAGE/JPEG')).toBe(true)
expect(isSupportedAttachmentType('application/pdf')).toBe(true)
})
it('rejects everything else, including a missing type', () => {
expect(isSupportedAttachmentType('application/x-sh')).toBe(false)
expect(isSupportedAttachmentType('image/heic')).toBe(false)
expect(isSupportedAttachmentType(undefined)).toBe(false)
expect(isSupportedAttachmentType('')).toBe(false)
})
it('offers the same list to the file picker', () => {
expect(SUPPORT_ATTACHMENT_ACCEPT).toBe('image/jpeg,image/png,image/webp,application/pdf')
})
it('allows five files with a four megabyte combined budget', () => {
expect(SUPPORT_MAX_ATTACHMENTS).toBe(5)
expect(SUPPORT_MAX_ATTACHMENT_TOTAL_MB).toBe(4)
expect(SUPPORT_MAX_ATTACHMENT_TOTAL_BYTES).toBe(4 * 1024 * 1024)
})
// Vercel kills the request before the route runs if the body is over its own
// ceiling, so the budget has to leave room for the multipart envelope.
it('stays under the platform request-body ceiling', () => {
expect(SUPPORT_MAX_ATTACHMENT_TOTAL_BYTES).toBeLessThan(HOSTED_REQUEST_BODY_LIMIT_BYTES)
})
describe('sanitizeAttachmentFilename', () => {
it('keeps an ordinary name', () => {
expect(sanitizeAttachmentFilename('skarmbild.png')).toBe('skarmbild.png')
})
it('drops path segments from both separators', () => {
expect(sanitizeAttachmentFilename('../../etc/passwd.png')).toBe('passwd.png')
expect(sanitizeAttachmentFilename('C:\\Users\\emil\\bild.png')).toBe('bild.png')
})
it('strips control characters that would break a mail header', () => {
const withNewline = `bild${String.fromCharCode(13)}${String.fromCharCode(10)}.png`
expect(sanitizeAttachmentFilename(withNewline)).toBe('bild.png')
})
it('falls back when nothing usable is left', () => {
expect(sanitizeAttachmentFilename('')).toBe('bilaga')
expect(sanitizeAttachmentFilename(null)).toBe('bilaga')
expect(sanitizeAttachmentFilename(String.fromCharCode(0))).toBe('bilaga')
})
it('bounds a very long name but keeps the extension', () => {
const long = `${'a'.repeat(300)}.png`
const result = sanitizeAttachmentFilename(long)
expect(result.length).toBeLessThanOrEqual(100)
expect(result.endsWith('.png')).toBe(true)
})
})
describe('supportAttachmentFilename', () => {
it('forces the extension to agree with the verified MIME type', () => {
expect(supportAttachmentFilename('update.exe', 'application/pdf')).toBe('update.pdf')
expect(supportAttachmentFilename('photo.png', 'image/jpeg')).toBe('photo.jpg')
})
it('keeps the final filename within the mail-header bound', () => {
const filename = supportAttachmentFilename('a'.repeat(150), 'application/pdf')
expect(filename.length).toBeLessThanOrEqual(100)
})
})
it('does not expose attachment names in session-replay attributes', () => {
const source = readFileSync(
join(process.cwd(), 'components', 'ui', 'support-link.tsx'),
'utf8'
)
expect(source).toContain('ph-no-capture')
expect(source).not.toContain('title={file.name}')
})
})