import { describe, it, expect, beforeEach } from 'vitest' import { useBacklogStore } from '@/stores/backlog-store' import type { BacklogPbi, BacklogStory, BacklogTask } from '@/stores/backlog-store' const PBI: BacklogPbi = { id: 'pbi-1', code: 'PBI-1', title: 'Realtime PBI', priority: 2, description: 'desc', created_at: new Date('2024-01-01T00:00:00Z'), status: 'ready', } const STORY: BacklogStory = { id: 'story-1', code: 'ST-1', title: 'Realtime story', description: null, acceptance_criteria: null, priority: 2, status: 'OPEN', pbi_id: 'pbi-1', sprint_id: null, created_at: new Date('2024-01-01T00:00:00Z'), } const TASK: BacklogTask = { id: 'task-1', title: 'Realtime task', description: null, priority: 2, status: 'TO_DO', sort_order: 1, story_id: 'story-1', created_at: new Date('2024-01-01T00:00:00Z'), } beforeEach(() => { useBacklogStore.setState({ pbis: [], storiesByPbi: {}, tasksByStory: {} }) }) // --------------------------------------------------------------------------- // PBI // --------------------------------------------------------------------------- describe('PBI payload contract', () => { it('INSERT: entity appears in pbis with correct title and status', () => { useBacklogStore.getState().applyChange('pbi', 'I', { ...PBI }) const state = useBacklogStore.getState() expect(state.pbis).toHaveLength(1) expect(state.pbis[0].id).toBe('pbi-1') expect(state.pbis[0].title).toBe('Realtime PBI') expect(state.pbis[0].status).toBe('ready') }) it('INSERT is idempotent: duplicate SSE-event does not add a second entry', () => { useBacklogStore.getState().applyChange('pbi', 'I', { ...PBI }) useBacklogStore.getState().applyChange('pbi', 'I', { ...PBI }) expect(useBacklogStore.getState().pbis).toHaveLength(1) }) it('UPDATE: changed_fields partial merges into existing entity', () => { useBacklogStore.setState({ pbis: [{ ...PBI }], storiesByPbi: {}, tasksByStory: {} }) useBacklogStore.getState().applyChange('pbi', 'U', { id: 'pbi-1', title: 'Updated PBI', status: 'in_sprint' as const }) const pbi = useBacklogStore.getState().pbis[0] expect(pbi.title).toBe('Updated PBI') expect(pbi.status).toBe('in_sprint') expect(pbi.priority).toBe(2) // unchanged field retained }) it('DELETE: entity is removed from pbis', () => { useBacklogStore.setState({ pbis: [{ ...PBI }], storiesByPbi: {}, tasksByStory: {} }) useBacklogStore.getState().applyChange('pbi', 'D', { id: 'pbi-1' }) expect(useBacklogStore.getState().pbis).toHaveLength(0) }) }) // --------------------------------------------------------------------------- // Story // --------------------------------------------------------------------------- describe('Story payload contract', () => { it('INSERT: entity appears in storiesByPbi[pbi_id] with correct title and status', () => { useBacklogStore.setState({ pbis: [], storiesByPbi: { 'pbi-1': [] }, tasksByStory: {} }) useBacklogStore.getState().applyChange('story', 'I', { ...STORY }) const bucket = useBacklogStore.getState().storiesByPbi['pbi-1'] expect(bucket).toHaveLength(1) expect(bucket[0].id).toBe('story-1') expect(bucket[0].title).toBe('Realtime story') expect(bucket[0].status).toBe('OPEN') }) it('INSERT: creates bucket when pbi_id was not yet in storiesByPbi', () => { useBacklogStore.getState().applyChange('story', 'I', { ...STORY }) expect(useBacklogStore.getState().storiesByPbi['pbi-1']).toHaveLength(1) }) it('INSERT is idempotent: duplicate SSE-event does not add a second entry', () => { useBacklogStore.getState().applyChange('story', 'I', { ...STORY }) useBacklogStore.getState().applyChange('story', 'I', { ...STORY }) expect(useBacklogStore.getState().storiesByPbi['pbi-1']).toHaveLength(1) }) it('UPDATE: changed_fields partial merges into existing story', () => { useBacklogStore.setState({ pbis: [], storiesByPbi: { 'pbi-1': [{ ...STORY }] }, tasksByStory: {} }) useBacklogStore.getState().applyChange('story', 'U', { id: 'story-1', title: 'Updated story', status: 'IN_SPRINT' }) const story = useBacklogStore.getState().storiesByPbi['pbi-1'][0] expect(story.title).toBe('Updated story') expect(story.status).toBe('IN_SPRINT') expect(story.priority).toBe(2) // unchanged field retained }) it('DELETE: entity is removed from its pbi bucket', () => { useBacklogStore.setState({ pbis: [], storiesByPbi: { 'pbi-1': [{ ...STORY }] }, tasksByStory: {} }) useBacklogStore.getState().applyChange('story', 'D', { id: 'story-1' }) expect(useBacklogStore.getState().storiesByPbi['pbi-1']).toHaveLength(0) }) }) // --------------------------------------------------------------------------- // Task // --------------------------------------------------------------------------- describe('Task payload contract', () => { it('INSERT: entity appears in tasksByStory[story_id] with correct title and status', () => { useBacklogStore.setState({ pbis: [], storiesByPbi: {}, tasksByStory: { 'story-1': [] } }) useBacklogStore.getState().applyChange('task', 'I', { ...TASK }) const bucket = useBacklogStore.getState().tasksByStory['story-1'] expect(bucket).toHaveLength(1) expect(bucket[0].id).toBe('task-1') expect(bucket[0].title).toBe('Realtime task') expect(bucket[0].status).toBe('TO_DO') }) it('INSERT: creates bucket when story_id was not yet in tasksByStory', () => { useBacklogStore.getState().applyChange('task', 'I', { ...TASK }) expect(useBacklogStore.getState().tasksByStory['story-1']).toHaveLength(1) }) it('INSERT is idempotent: duplicate SSE-event does not add a second entry', () => { useBacklogStore.getState().applyChange('task', 'I', { ...TASK }) useBacklogStore.getState().applyChange('task', 'I', { ...TASK }) expect(useBacklogStore.getState().tasksByStory['story-1']).toHaveLength(1) }) it('UPDATE: changed_fields partial merges into existing task', () => { useBacklogStore.setState({ pbis: [], storiesByPbi: {}, tasksByStory: { 'story-1': [{ ...TASK }] } }) useBacklogStore.getState().applyChange('task', 'U', { id: 'task-1', title: 'Updated task', status: 'IN_PROGRESS' }) const task = useBacklogStore.getState().tasksByStory['story-1'][0] expect(task.title).toBe('Updated task') expect(task.status).toBe('IN_PROGRESS') expect(task.sort_order).toBe(1) // unchanged field retained }) it('DELETE: entity is removed from its story bucket', () => { useBacklogStore.setState({ pbis: [], storiesByPbi: {}, tasksByStory: { 'story-1': [{ ...TASK }] } }) useBacklogStore.getState().applyChange('task', 'D', { id: 'task-1' }) expect(useBacklogStore.getState().tasksByStory['story-1']).toHaveLength(0) }) })