d3ro-voice/apps/desktop/tests/red/memo.usecase.test.ts
Yun Chan 708e20f747
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped
feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
2026-08-20 11:12:05 +09:00

145 lines
5.3 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { D3ROError, ErrorCode } from '@d3ro/core/errors'
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
import { getHistoryService } from '../../src/main/services/HistoryService'
import { getMemoService } from '../../src/main/services/MemoService'
import { registerMemoHandlers } from '../../src/main/ipc/memo-handlers'
import { historyInput, invokeIpc, useRedHarness } from './harness'
useRedHarness()
describe('유스케이스: 메모 태그 추가/삭제/검색/내보내기', () => {
function seedHistory(): string {
return getHistoryService().create(historyInput()).id
}
it('새 히스토리에는 태그가 없다', () => {
const id = seedHistory()
expect(getMemoService().getTagsForEntry(id)).toEqual([])
})
it('태그를 달면 소문자로 정규화된다', () => {
const id = seedHistory()
const tag = getMemoService().addTag(id, ' IDEA ')
expect(tag.tag).toBe('idea')
expect(getMemoService().getTagsForEntry(id)).toHaveLength(1)
})
it('같은 태그 중복은 MemoTagDuplicate 다', () => {
const id = seedHistory()
getMemoService().addTag(id, 'work')
expect(() => getMemoService().addTag(id, 'WORK')).toThrow(D3ROError)
try {
getMemoService().addTag(id, 'work')
} catch (err) {
expect((err as D3ROError).code).toBe(ErrorCode.MemoTagDuplicate)
}
})
it('빈 태그는 거부된다', () => {
const id = seedHistory()
expect(() => getMemoService().addTag(id, ' ')).toThrow()
})
it('태그 제거 후 목록이 비다', () => {
const id = seedHistory()
getMemoService().addTag(id, 'tmp')
getMemoService().removeTag(id, 'tmp')
expect(getMemoService().getTagsForEntry(id)).toEqual([])
})
it('없는 태그 제거는 MemoTagNotFound 다', () => {
const id = seedHistory()
expect(() => getMemoService().removeTag(id, 'ghost')).toThrow(D3ROError)
try {
getMemoService().removeTag(id, 'ghost')
} catch (err) {
expect((err as D3ROError).code).toBe(ErrorCode.MemoTagNotFound)
}
})
it('전체 태그 카운트가 사용 횟수를 반영한다', () => {
const a = seedHistory()
const b = seedHistory()
getMemoService().addTag(a, 'shared')
getMemoService().addTag(b, 'shared')
getMemoService().addTag(a, 'solo')
const all = getMemoService().getAllTags()
const shared = all.find((t) => t.tag === 'shared')
expect(shared?.count).toBe(2)
})
it('태그로 히스토리를 검색한다', () => {
const a = seedHistory()
seedHistory()
getMemoService().addTag(a, 'findme')
const page = getMemoService().searchByTag({ tag: 'findme', page: 0, pageSize: 10 })
expect(page.total).toBe(1)
expect(page.entries[0].id).toBe(a)
})
it('없는 태그 검색은 0건이다', () => {
seedHistory()
expect(getMemoService().searchByTag({ tag: 'none', page: 0, pageSize: 10 }).total).toBe(0)
})
it('마크다운 내보내기는 파일을 만들고 내용을 담는다', () => {
const id = seedHistory()
getMemoService().addTag(id, 'export')
const filePath = getMemoService().exportMarkdown({ tag: 'export' })
expect(filePath.endsWith('.md')).toBe(true)
const fs = require('fs') as typeof import('fs')
const body = fs.readFileSync(filePath, 'utf-8')
expect(body).toContain('#export')
expect(body.length).toBeGreaterThan(10)
})
it('매칭 없는 내보내기도 파일을 만들되 비었음을 표시한다', () => {
const filePath = getMemoService().exportMarkdown({ tag: 'absent' })
const fs = require('fs') as typeof import('fs')
const body = fs.readFileSync(filePath, 'utf-8')
expect(body).toMatch(/No memo entries found/i)
})
it('IPC memo:addTag / getTags 왕복', async () => {
registerMemoHandlers()
const id = seedHistory()
const add = await invokeIpc(IPC_CHANNELS.MEMO.ADD_TAG, { historyId: id, tag: 'ipc' })
expect(add.success).toBe(true)
const tags = await invokeIpc(IPC_CHANNELS.MEMO.GET_TAGS, { historyId: id })
expect(tags.success).toBe(true)
if (tags.success) expect(tags.data[0].tag).toBe('ipc')
})
it('IPC 중복 태그는 MemoTagDuplicate 다', async () => {
registerMemoHandlers()
const id = seedHistory()
await invokeIpc(IPC_CHANNELS.MEMO.ADD_TAG, { historyId: id, tag: 'dup' })
const res = await invokeIpc(IPC_CHANNELS.MEMO.ADD_TAG, { historyId: id, tag: 'dup' })
expect(res.success).toBe(false)
if (!res.success) expect(res.error.code).toBe(ErrorCode.MemoTagDuplicate)
})
it('IPC 없는 태그 제거는 MemoTagNotFound 다', async () => {
registerMemoHandlers()
const id = seedHistory()
const res = await invokeIpc(IPC_CHANNELS.MEMO.REMOVE_TAG, { historyId: id, tag: 'nope' })
expect(res.success).toBe(false)
if (!res.success) expect(res.error.code).toBe(ErrorCode.MemoTagNotFound)
})
it('IPC getAllTags / searchByTag', async () => {
registerMemoHandlers()
const id = seedHistory()
getMemoService().addTag(id, 'listed')
const all = await invokeIpc(IPC_CHANNELS.MEMO.GET_ALL_TAGS)
expect(all.success).toBe(true)
const search = await invokeIpc(IPC_CHANNELS.MEMO.SEARCH_BY_TAG, {
tag: 'listed',
page: 0,
pageSize: 10,
})
expect(search.success).toBe(true)
if (search.success) expect(search.data.total).toBe(1)
})
})