File size: 3,956 Bytes
ceb943f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { detectObjects } from '../detect-objects';
import { extractFramesAtIntervals } from '@/features/discovery/services/frame-extraction.service';
import { detectObjectsInFrame } from '@/features/discovery/services/ai-vision.service';
import { db } from '@/lib/db';
import { uploadThumbnail } from '@/features/discovery/services/storage.service';

// Mock dependencies
vi.mock('@/features/discovery/services/storage.service', () => ({
    uploadThumbnail: vi.fn().mockResolvedValue('https://supa-storage.com/thumb.jpg')
}));

vi.mock('sharp', () => {
    return {
        default: vi.fn(() => ({
            metadata: vi.fn().mockResolvedValue({ width: 1920, height: 1080 }),
            extract: vi.fn().mockReturnThis(),
            resize: vi.fn().mockReturnThis(),
            toFormat: vi.fn().mockReturnThis(),
            toBuffer: vi.fn().mockResolvedValue(Buffer.from('thumb')),
        }))
    };
});
vi.mock('@/features/discovery/services/frame-extraction.service', () => ({
    extractFramesAtIntervals: vi.fn(),
    VideoUnavailableError: class VideoUnavailableError extends Error {
        constructor(message: string) {
            super(message);
            this.name = 'VideoUnavailableError';
        }
    }
}));

vi.mock('@/features/discovery/services/ai-vision.service', () => ({
    detectObjectsInFrame: vi.fn(),
    VisionRateLimitError: class VisionRateLimitError extends Error { },
}));

vi.mock('@/lib/db', () => ({
    db: {
        insert: vi.fn(() => ({
            values: vi.fn(() => ({
                returning: vi.fn().mockResolvedValue([{ id: 'det-1' }, { id: 'det-2' }]),
            })),
        })),
        update: vi.fn(() => ({
            set: vi.fn(() => ({
                where: vi.fn().mockResolvedValue(undefined),
            })),
        })),
        query: {
            youtubeVideos: {
                findFirst: vi.fn().mockResolvedValue({ id: 'v123', duration: 33 }),
            },
        },
    },
}));

vi.mock('@/lib/db/schema', () => ({
    detectedObjects: {},
    youtubeVideos: {},
}));

// Mock Inngest client to expose handler
vi.mock('../../client', () => ({
    inngest: {
        createFunction: vi.fn((config, trigger, handler) => ({ config, trigger, handler, fn: handler })),
        send: vi.fn().mockResolvedValue(undefined),
    }
}));

vi.mock('@sentry/nextjs', () => ({
    captureException: vi.fn(),
}));

describe('Detect Objects Inngest Function', () => {
    beforeEach(() => {
        vi.clearAllMocks();
    });

    it('should be defined', () => {
        expect(detectObjects).toBeDefined();
    });

    it('should execute analysis and save results', async () => {
        // Setup mocks
        vi.mocked(extractFramesAtIntervals).mockResolvedValue([
            { timestamp: 0, frameBuffer: Buffer.from('f1') },
            { timestamp: 10, frameBuffer: Buffer.from('f2') }
        ]);

        vi.mocked(detectObjectsInFrame).mockResolvedValue([
            { name: 'Obj1', category: 'Tech', confidence: 0.9, boundingBox: { x: 10, y: 10, width: 100, height: 100 } }
        ]);

        const stepMock = {
            run: vi.fn(async (_name: string, fn: () => Promise<any>) => {
                return await fn();
            })
        };

        const event = {
            data: {
                videoId: 'v123',
                videoUrl: 'http://video.com'
            }
        };

        // Execute handler
        const result = await detectObjects.fn({ event, step: stepMock });

        expect(result.success).toBe(true);
        expect(result.count).toBe(2); // 2 frames × 1 detection each

        expect(extractFramesAtIntervals).toHaveBeenCalledWith('http://video.com', 5); // Adaptive: 5s for 33s video
        expect(detectObjectsInFrame).toHaveBeenCalledTimes(2);
        expect(uploadThumbnail).toHaveBeenCalledTimes(2);
        expect(db.insert).toHaveBeenCalled();
    });
});