feed.test.js
1 import * as fs from 'node:fs'; 2 import * as os from 'node:os'; 3 import * as path from 'node:path'; 4 import { afterEach, describe, expect, it } from 'vitest'; 5 import { __test__ } from './feed.js'; 6 describe('linux-do feed metadata resolution', () => { 7 afterEach(() => { 8 __test__.resetMetadataCaches(); 9 }); 10 it('prefers live tag metadata over the bundled snapshot', async () => { 11 __test__.setLiveMetadataForTests({ 12 tags: [{ id: 9999, slug: 'fresh-tag', name: 'Fresh Tag' }], 13 }); 14 const request = await __test__.resolveFeedRequest(null, { 15 tag: 'Fresh Tag', 16 view: 'latest', 17 limit: 20, 18 }); 19 expect(request.url).toBe('/tag/fresh-tag/9999.json?per_page=20'); 20 }); 21 it('uses live category metadata with parent paths for subcategories', async () => { 22 __test__.setLiveMetadataForTests({ 23 categories: [ 24 { 25 id: 10, 26 name: 'Parent', 27 description: '', 28 slug: 'parent', 29 parentCategoryId: null, 30 parent: null, 31 }, 32 { 33 id: 11, 34 name: 'Fresh Child', 35 description: '', 36 slug: 'fresh-child', 37 parentCategoryId: 10, 38 parent: { 39 id: 10, 40 name: 'Parent', 41 description: '', 42 slug: 'parent', 43 parentCategoryId: null, 44 }, 45 }, 46 ], 47 }); 48 const request = await __test__.resolveFeedRequest(null, { 49 category: 'Fresh Child', 50 view: 'hot', 51 limit: 20, 52 }); 53 expect(request.url).toBe('/c/parent/fresh-child/11/l/hot.json?per_page=20'); 54 }); 55 it('accepts parent/name category paths for subcategories', async () => { 56 __test__.setLiveMetadataForTests({ 57 categories: [ 58 { 59 id: 10, 60 name: 'Parent', 61 description: '', 62 slug: 'parent', 63 parentCategoryId: null, 64 parent: null, 65 }, 66 { 67 id: 11, 68 name: 'Fresh Child', 69 description: '', 70 slug: 'fresh-child', 71 parentCategoryId: 10, 72 parent: { 73 id: 10, 74 name: 'Parent', 75 description: '', 76 slug: 'parent', 77 parentCategoryId: null, 78 }, 79 }, 80 ], 81 }); 82 const request = await __test__.resolveFeedRequest(null, { 83 category: 'Parent / Fresh Child', 84 view: 'latest', 85 limit: 20, 86 }); 87 expect(request.url).toBe('/c/parent/fresh-child/11.json?per_page=20'); 88 }); 89 it('falls back to cached metadata when live metadata is unavailable', async () => { 90 const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-linux-do-cache-')); 91 __test__.setCacheDirForTests(cacheDir); 92 fs.writeFileSync(path.join(cacheDir, 'tags.json'), JSON.stringify({ 93 fetchedAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), 94 data: [{ id: 3, slug: 'chatgpt', name: 'ChatGPT' }], 95 })); 96 fs.writeFileSync(path.join(cacheDir, 'categories.json'), JSON.stringify({ 97 fetchedAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), 98 data: [{ 99 id: 4, 100 name: '开发调优', 101 description: '', 102 slug: 'develop', 103 parentCategoryId: null, 104 parent: null, 105 }], 106 })); 107 const request = await __test__.resolveFeedRequest(null, { 108 tag: 'ChatGPT', 109 category: '开发调优', 110 view: 'top', 111 period: 'monthly', 112 limit: 20, 113 }); 114 expect(request.url).toContain('/tags/c/develop/4/chatgpt/3/l/top.json'); 115 expect(request.url).toContain('per_page=20'); 116 expect(request.url).toContain('period=monthly'); 117 }); 118 });