|
1 | 1 | import { describe, it, expect, vi, beforeEach } from 'vitest' |
2 | 2 | import { normalise, normalizeStats } from '../normalise' |
3 | | -import type { ManualProjectInput, GitHubProjectInput, NpmProjectInput, ProductHuntProjectInput } from '../../types' |
| 3 | +import type { ManualProjectInput, GitHubProjectInput, NpmProjectInput, ProductHuntProjectInput, YouTubeProjectInput } from '../../types' |
4 | 4 | import { fetchGitHubRepo, fetchGitHubCommits } from '../github' |
5 | 5 | import { fetchNpmPackage } from '../npm' |
6 | 6 | import { fetchProductHuntPost } from '../product-hunt' |
| 7 | +import { fetchYouTubeChannel } from '../youtube' |
7 | 8 |
|
8 | 9 | vi.mock('../github') |
9 | 10 | vi.mock('../npm') |
10 | 11 | vi.mock('../product-hunt') |
| 12 | +vi.mock('../youtube') |
11 | 13 |
|
12 | 14 | const mockedFetchGitHubRepo = vi.mocked(fetchGitHubRepo) |
13 | 15 | const mockedFetchGitHubCommits = vi.mocked(fetchGitHubCommits) |
14 | 16 | const mockedFetchNpmPackage = vi.mocked(fetchNpmPackage) |
15 | 17 | const mockedFetchProductHuntPost = vi.mocked(fetchProductHuntPost) |
| 18 | +const mockedFetchYouTubeChannel = vi.mocked(fetchYouTubeChannel) |
16 | 19 |
|
17 | 20 | describe('normalise', () => { |
18 | 21 | beforeEach(() => { |
@@ -783,6 +786,115 @@ describe('normalise', () => { |
783 | 786 | expect(result.name).toBe('Fallback Name') |
784 | 787 | expect(result.stats).toBeNull() |
785 | 788 | }) |
| 789 | + |
| 790 | + it('should use featured_at as updatedAt for Product Hunt projects', async () => { |
| 791 | + mockedFetchProductHuntPost.mockResolvedValue({ |
| 792 | + name: 'Product Name', |
| 793 | + tagline: 'Product tagline', |
| 794 | + description: 'Product description', |
| 795 | + votes_count: 500, |
| 796 | + comments_count: 50, |
| 797 | + featured_at: '2024-03-15T00:00:00Z', |
| 798 | + website: 'https://example.com', |
| 799 | + url: 'https://producthunt.com/posts/product', |
| 800 | + }) |
| 801 | + |
| 802 | + const input: ProductHuntProjectInput = { |
| 803 | + id: 'test-ph-timestamp', |
| 804 | + type: 'product-hunt', |
| 805 | + slug: 'my-product', |
| 806 | + status: 'shipped', |
| 807 | + name: 'Input Name', |
| 808 | + createdAt: '2024-01-01', |
| 809 | + updatedAt: '2024-02-01', |
| 810 | + } |
| 811 | + |
| 812 | + const result = await normalise(input) |
| 813 | + |
| 814 | + expect(result.createdAt).toBe('2024-01-01') |
| 815 | + expect(result.updatedAt).toBe('2024-03-15T00:00:00Z') |
| 816 | + }) |
| 817 | + |
| 818 | + it('should fallback to input updatedAt when Product Hunt fetch fails', async () => { |
| 819 | + mockedFetchProductHuntPost.mockResolvedValue(null) |
| 820 | + |
| 821 | + const input: ProductHuntProjectInput = { |
| 822 | + id: 'test-ph-fallback', |
| 823 | + type: 'product-hunt', |
| 824 | + slug: 'nonexistent', |
| 825 | + status: 'active', |
| 826 | + name: 'Fallback Name', |
| 827 | + updatedAt: '2024-05-01', |
| 828 | + } |
| 829 | + |
| 830 | + const result = await normalise(input) |
| 831 | + |
| 832 | + expect(result.updatedAt).toBe('2024-05-01') |
| 833 | + }) |
| 834 | + }) |
| 835 | + |
| 836 | + describe('YouTube project normalization', () => { |
| 837 | + beforeEach(() => { |
| 838 | + vi.clearAllMocks() |
| 839 | + }) |
| 840 | + |
| 841 | + it('should use latestVideoPublishedAt as updatedAt for YouTube projects', async () => { |
| 842 | + mockedFetchYouTubeChannel.mockResolvedValue({ |
| 843 | + subscriberCount: 10000, |
| 844 | + viewCount: 500000, |
| 845 | + latestVideoTitle: 'My Latest Video', |
| 846 | + latestVideoUrl: 'https://youtube.com/watch?v=abc123', |
| 847 | + latestVideoPublishedAt: '2024-04-20T10:00:00Z', |
| 848 | + }) |
| 849 | + |
| 850 | + const input: YouTubeProjectInput = { |
| 851 | + id: 'test-youtube-timestamp', |
| 852 | + type: 'youtube', |
| 853 | + channelId: 'UC_x5XG1OV2P6uZZ5FSM9Ttw', |
| 854 | + status: 'active', |
| 855 | + createdAt: '2024-01-01', |
| 856 | + updatedAt: '2024-02-01', |
| 857 | + } |
| 858 | + |
| 859 | + const result = await normalise(input) |
| 860 | + |
| 861 | + expect(result.createdAt).toBe('2024-01-01') |
| 862 | + expect(result.updatedAt).toBe('2024-04-20T10:00:00Z') |
| 863 | + }) |
| 864 | + |
| 865 | + it('should fallback to input updatedAt when YouTube fetch fails', async () => { |
| 866 | + mockedFetchYouTubeChannel.mockResolvedValue(null) |
| 867 | + |
| 868 | + const input: YouTubeProjectInput = { |
| 869 | + id: 'test-youtube-fallback', |
| 870 | + type: 'youtube', |
| 871 | + channelId: 'UC_x5XG1OV2P6uZZ5FSM9Ttw', |
| 872 | + status: 'active', |
| 873 | + name: 'My Channel', |
| 874 | + updatedAt: '2024-05-15', |
| 875 | + } |
| 876 | + |
| 877 | + const result = await normalise(input) |
| 878 | + |
| 879 | + expect(result.updatedAt).toBe('2024-05-15') |
| 880 | + }) |
| 881 | + |
| 882 | + it('should have no automatic timestamp when both fetch fails and no input', async () => { |
| 883 | + mockedFetchYouTubeChannel.mockResolvedValue(null) |
| 884 | + |
| 885 | + const input: YouTubeProjectInput = { |
| 886 | + id: 'test-youtube-no-timestamp', |
| 887 | + type: 'youtube', |
| 888 | + channelId: 'UC_x5XG1OV2P6uZZ5FSM9Ttw', |
| 889 | + status: 'active', |
| 890 | + name: 'My Channel', |
| 891 | + } |
| 892 | + |
| 893 | + const result = await normalise(input) |
| 894 | + |
| 895 | + expect(result.createdAt).toBeNull() |
| 896 | + expect(result.updatedAt).toBeNull() |
| 897 | + }) |
786 | 898 | }) |
787 | 899 |
|
788 | 900 | describe('hybrid project normalization', () => { |
|
0 commit comments