Files
superwork/vscode/src/panel/handlers/handoff.test.ts
T
cruldra b62e58d739 ✨ feat(vscode): 新增第五种工单会话「原型会话」
照测试会话那条链路补一份 prototype:

- 消息与路由:session/start-prototype、session/resume-prototype、
  issue/update-prototype-profile-path,都进只读门禁
- 会话:handleStartPrototypeSession / handleResumePrototypeSession,
  终端名 issue-N-原型;cwd 有 worktree 用 worktree、没有退主仓库根
  (原 resolveTestSessionCwd 改名 resolveWorktreeOrRootCwd 两种会话共用)
- 不要求有 PR,任何阶段都能起;前置工单锁仍然拦
- 提示词:新增 prompts/prototype.md,设置里可改,占位符 {issueNumber}
- profile:工单级 prototypeProfilePath > 设置里的原型默认 profile >
  内置默认,与其它会话互不回退
- 状态字段 prototypeSessionId / prototypeProfilePath / prototypeTabOpen
  贯通 gitea、youtrack、本机 state 与移交包
- 详情面板加「原型会话id」「原型配置文件」两行,设置面板加对应两项

Claude-Session: https://claude.ai/code/session_01Kgmb1LvrPmFX3NoUDB4zZC
2026-09-14 10:20:30 +08:00

201 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest'
import {
canAcceptHandoff,
canStartHandoff,
checkWorktreePushed,
handoffAcceptedStateExtra,
handoffAcceptedUiPatch,
handoffLocalTombstones,
handoffStartedStateExtra,
handoffStartedUiPatch,
} from './handoff'
describe('canStartHandoff', () => {
it('gitea 工单、非 done、无待接管、无 assignee 或 assignee 含我 → true', () => {
expect(canStartHandoff({ column: 'in-progress' }, 'me')).toBe(true)
expect(canStartHandoff({ column: 'todo', source: 'gitea' }, 'me')).toBe(true)
expect(canStartHandoff({ column: 'todo', assignees: [] }, 'me')).toBe(true)
expect(canStartHandoff({ column: 'todo', assignees: ['me', 'x'] }, 'me')).toBe(true)
})
it('done / youtrack / 已在移交中 / 不是我负责 → false', () => {
expect(canStartHandoff({ column: 'done' }, 'me')).toBe(false)
expect(canStartHandoff({ column: 'review', source: 'youtrack' }, 'me')).toBe(false)
expect(canStartHandoff({ column: 'review', handoffAttachmentId: '9' }, 'me')).toBe(false)
expect(canStartHandoff({ column: 'todo', assignees: ['x'] }, 'me')).toBe(false)
expect(canStartHandoff({ column: 'todo', assignees: ['me'] }, undefined)).toBe(false)
})
})
describe('canAcceptHandoff', () => {
it('有附件且 assignee 含我 → true', () => {
expect(canAcceptHandoff({ handoffAttachmentId: '9', assignees: ['me', 'x'] }, 'me')).toBe(true)
})
it('无附件 / 不是我的 / 身份未知 / youtrack → false', () => {
expect(canAcceptHandoff({ assignees: ['me'] }, 'me')).toBe(false)
expect(canAcceptHandoff({ handoffAttachmentId: '9', assignees: ['x'] }, 'me')).toBe(false)
expect(canAcceptHandoff({ handoffAttachmentId: '9', assignees: ['me'] }, undefined)).toBe(false)
expect(canAcceptHandoff({ source: 'youtrack', handoffAttachmentId: '9', assignees: ['me'] }, 'me')).toBe(false)
})
})
describe('checkWorktreePushed', () => {
it('干净且 HEAD 与远端一致 → ok', () => {
expect(checkWorktreePushed({ statusPorcelain: '', localHead: 'abc', remoteHead: 'abc' })).toEqual({ ok: true })
})
it('有未提交改动 → 拒绝', () => {
const r = checkWorktreePushed({ statusPorcelain: ' M a.ts\n', localHead: 'abc', remoteHead: 'abc' })
expect(r.ok).toBe(false)
if (!r.ok)
expect(r.reason).toMatch(/未提交/)
})
it('本地领先远端 → 拒绝', () => {
const r = checkWorktreePushed({ statusPorcelain: '', localHead: 'abc', remoteHead: 'def' })
expect(r.ok).toBe(false)
if (!r.ok)
expect(r.reason).toMatch(/push/)
})
})
describe('payloads', () => {
it('移交落盘:只写两个共享字段,本机字段留到本机清理跑完后再墓碑', () => {
expect(handoffStartedStateExtra(9, 'chw')).toEqual({
handoffAttachmentId: '9',
handoffFrom: 'chw',
})
})
it('本机墓碑:keepWorktreePath=false 时本机字段全清', () => {
expect(handoffLocalTombstones({ keepWorktreePath: false })).toEqual({
sessionId: '',
implementSessionId: '',
reviewSessionId: '',
testSessionId: '',
prototypeSessionId: '',
profilePath: '',
brainstormProfilePath: '',
testProfilePath: '',
prototypeProfilePath: '',
worktreePath: '',
prDiffFile: '',
})
})
it('本机墓碑:keepWorktreePath=true 时保留 worktreePath', () => {
const extra = handoffLocalTombstones({ keepWorktreePath: true })
expect(extra.worktreePath).toBeUndefined()
expect(extra).toEqual({
sessionId: '',
implementSessionId: '',
reviewSessionId: '',
testSessionId: '',
prototypeSessionId: '',
profilePath: '',
brainstormProfilePath: '',
testProfilePath: '',
prototypeProfilePath: '',
prDiffFile: '',
})
})
it('移交 UI patch 用 null 清字段并更新 assignees', () => {
expect(handoffStartedUiPatch(9, 'chw', 'me', { keepWorktreePath: false })).toEqual({
handoffAttachmentId: '9',
handoffFrom: 'chw',
assignees: ['me'],
sessionId: null,
implementSessionId: null,
reviewSessionId: null,
testSessionId: null,
prototypeSessionId: null,
profilePath: null,
brainstormProfilePath: null,
testProfilePath: null,
prototypeProfilePath: null,
prDiffFile: null,
worktreePath: null,
worktreeExists: false,
})
})
it('移交 UI patch:keepWorktreePath=true 时省略 worktreePath / worktreeExists', () => {
const patch = handoffStartedUiPatch(9, 'chw', 'me', { keepWorktreePath: true })
expect(patch).not.toHaveProperty('worktreePath')
expect(patch).not.toHaveProperty('worktreeExists')
expect(patch).toEqual({
handoffAttachmentId: '9',
handoffFrom: 'chw',
assignees: ['me'],
sessionId: null,
implementSessionId: null,
reviewSessionId: null,
testSessionId: null,
prototypeSessionId: null,
profilePath: null,
brainstormProfilePath: null,
testProfilePath: null,
prototypeProfilePath: null,
prDiffFile: null,
})
})
it('接管落盘:清两个共享字段,写本机 sid / profile / worktree', () => {
expect(handoffAcceptedStateExtra({
sessions: { implementSessionId: 'i', reviewSessionId: 'r' },
profiles: { profilePath: '/p.json' },
worktreePath: '/wt',
})).toEqual({
handoffAttachmentId: '',
handoffFrom: '',
implementSessionId: 'i',
reviewSessionId: 'r',
profilePath: '/p.json',
worktreePath: '/wt',
})
})
it('接管 UI patch', () => {
expect(handoffAcceptedUiPatch({
sessions: { sessionId: 's' },
profiles: {},
worktreePath: '/wt',
worktreeExists: true,
})).toEqual({
handoffAttachmentId: null,
handoffFrom: null,
sessionId: 's',
worktreePath: '/wt',
worktreeExists: true,
})
})
it('接管 UI patch:无 worktreePath 时显式置 null,不留旧值', () => {
expect(handoffAcceptedUiPatch({
sessions: { sessionId: 's' },
profiles: {},
worktreeExists: false,
})).toEqual({
handoffAttachmentId: null,
handoffFrom: null,
sessionId: 's',
worktreePath: null,
worktreeExists: false,
})
})
it('接管 UI patch:带 reviewSessionFileExists 时一并下发,UI 不用等下次刷新才能点链接', () => {
expect(handoffAcceptedUiPatch({
sessions: { reviewSessionId: 'r' },
profiles: {},
worktreeExists: false,
reviewSessionFileExists: true,
})).toEqual({
handoffAttachmentId: null,
handoffFrom: null,
reviewSessionId: 'r',
worktreePath: null,
worktreeExists: false,
reviewSessionFileExists: true,
})
})
it('接管 UI patch:reviewSessionFileExists 未传时不出现在 patch 里', () => {
const patch = handoffAcceptedUiPatch({
sessions: { sessionId: 's' },
profiles: {},
worktreeExists: false,
})
expect(patch).not.toHaveProperty('reviewSessionFileExists')
})
})