d3ro-voice/apps/desktop/tests/red/memo-export-redteam-r1-10.test.ts

68 lines
2.6 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import fs from 'fs'
import path from 'path'
import { getHistoryService } from '../../src/main/services/HistoryService'
import { getMemoService } from '../../src/main/services/MemoService'
import {
buildMemoExportFileName,
sanitizeMemoExportTag,
MEMO_EXPORT_TAG_MAX_LENGTH
} from '../../src/main/services/memo/memo-export-file-name'
import { historyInput, useRedHarness } from './harness'
useRedHarness()
describe('메모 내보내기 파일명 정책 (순수)', () => {
it('태그가 없으면 memo_<ts>.md', () => {
expect(buildMemoExportFileName(undefined, 123)).toBe('memo_123.md')
expect(buildMemoExportFileName('', 123)).toBe('memo_123.md')
})
it('경로 구분자·NTFS 예약 문자·제어 문자는 _ 로 바뀐다', () => {
expect(sanitizeMemoExportTag('c/c++')).toBe('c_c++')
expect(sanitizeMemoExportTag('q:a')).toBe('q_a')
expect(sanitizeMemoExportTag('a\\b*c?d"e<f>g|h')).toBe('a_b_c_d_e_f_g_h')
expect(sanitizeMemoExportTag('x\u0001y\u007fz')).toBe('x_y_z')
})
it('.. 탐색 조각은 남지 않는다', () => {
const name = buildMemoExportFileName('a/../../../x', 1)
expect(name).not.toMatch(/[\\/]/)
expect(name).not.toContain('..')
expect(path.basename(name)).toBe(name)
})
it('한글 태그는 유지되고 공백은 _ 로 바뀐다', () => {
expect(sanitizeMemoExportTag('회의 메모')).toBe('회의_메모')
})
it('길이가 제한된다', () => {
expect(Array.from(sanitizeMemoExportTag('가'.repeat(500))).length).toBe(MEMO_EXPORT_TAG_MAX_LENGTH)
})
it('안전한 문자가 하나도 없으면 접미사를 생략한다', () => {
expect(buildMemoExportFileName('///', 7)).toBe('memo_7.md')
expect(buildMemoExportFileName('..', 7)).toBe('memo_7.md')
})
})
describe('유스케이스: 특수 문자 태그로 메모 내보내기', () => {
function exportWithTag(tag: string): string {
const id = getHistoryService().create(historyInput()).id
getMemoService().addTag(id, tag)
return getMemoService().exportMarkdown({ tag })
}
it.each(['c/c++', 'q:a', 'a/../../../x', 'what?*|<>"'])(
'태그 %s 도 exports 폴더 바로 아래에 보이는 파일로 저장된다',
(tag) => {
const filePath = exportWithTag(tag)
expect(path.basename(path.dirname(filePath))).toBe('exports')
expect(path.basename(filePath)).not.toMatch(/[\\/:*?"<>|]/)
expect(fs.existsSync(filePath)).toBe(true)
const listed = fs.readdirSync(path.dirname(filePath))
expect(listed).toContain(path.basename(filePath))
expect(fs.readFileSync(filePath, 'utf-8')).toContain(`#${tag.toLowerCase()}`)
}
)
})