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_.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"eg|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()}`) } ) })