discussion.test.js
1 import { describe, expect, it, vi } from 'vitest'; 2 import { AuthRequiredError } from '@jackwener/opencli/errors'; 3 import { getRegistry } from '@jackwener/opencli/registry'; 4 import { __test__ } from './discussion.js'; 5 import './discussion.js'; 6 7 function createPageMock(evaluateResults) { 8 const evaluate = vi.fn(); 9 for (const result of evaluateResults) { 10 evaluate.mockResolvedValueOnce(result); 11 } 12 return { 13 goto: vi.fn().mockResolvedValue(undefined), 14 wait: vi.fn().mockResolvedValue(undefined), 15 evaluate, 16 snapshot: vi.fn().mockResolvedValue(undefined), 17 click: vi.fn().mockResolvedValue(undefined), 18 typeText: vi.fn().mockResolvedValue(undefined), 19 pressKey: vi.fn().mockResolvedValue(undefined), 20 scrollTo: vi.fn().mockResolvedValue(undefined), 21 getFormState: vi.fn().mockResolvedValue({ forms: [], orphanFields: [] }), 22 tabs: vi.fn().mockResolvedValue([]), 23 selectTab: vi.fn().mockResolvedValue(undefined), 24 networkRequests: vi.fn().mockResolvedValue([]), 25 consoleMessages: vi.fn().mockResolvedValue([]), 26 scroll: vi.fn().mockResolvedValue(undefined), 27 autoScroll: vi.fn().mockResolvedValue(undefined), 28 installInterceptor: vi.fn().mockResolvedValue(undefined), 29 getInterceptedRequests: vi.fn().mockResolvedValue([]), 30 getCookies: vi.fn().mockResolvedValue([]), 31 screenshot: vi.fn().mockResolvedValue(''), 32 waitForCapture: vi.fn().mockResolvedValue(undefined), 33 }; 34 } 35 36 describe('amazon discussion normalization', () => { 37 it('normalizes review summary and sample reviews', () => { 38 const result = __test__.normalizeDiscussionPayload({ 39 href: 'https://www.amazon.com/product-reviews/B0FJS72893', 40 average_rating_text: '3.9 out of 5', 41 total_review_count_text: '27 global ratings', 42 qa_links: [], 43 review_samples: [ 44 { 45 title: '5.0 out of 5 stars Great value and quality', 46 rating_text: '5.0 out of 5 stars', 47 author: 'GTreader2', 48 date_text: 'Reviewed in the United States on February 21, 2026', 49 body: 'Small but mighty.', 50 verified: true, 51 }, 52 ], 53 }); 54 55 expect(result.asin).toBe('B0FJS72893'); 56 expect(result.average_rating_value).toBe(3.9); 57 expect(result.total_review_count).toBe(27); 58 expect(result.review_samples).toEqual([ 59 { 60 title: 'Great value and quality', 61 rating_text: '5.0 out of 5 stars', 62 rating_value: 5, 63 author: 'GTreader2', 64 date_text: 'Reviewed in the United States on February 21, 2026', 65 body: 'Small but mighty.', 66 verified_purchase: true, 67 }, 68 ]); 69 }); 70 71 it('falls back to the product page when the review page redirects to sign-in', async () => { 72 const command = getRegistry().get('amazon/discussion'); 73 const page = createPageMock([ 74 { 75 href: 'https://www.amazon.com/ap/signin?openid.return_to=https%3A%2F%2Fwww.amazon.com%2Fproduct-reviews%2FB09HKN2ZRT', 76 title: 'Amazon Sign-In', 77 body_text: 'Sign in Create account', 78 }, 79 { 80 href: 'https://www.amazon.com/ap/signin?openid.return_to=https%3A%2F%2Fwww.amazon.com%2Fproduct-reviews%2FB09HKN2ZRT', 81 average_rating_text: '', 82 total_review_count_text: '', 83 review_samples: [], 84 }, 85 { 86 href: 'https://www.amazon.com/dp/B09HKN2ZRT', 87 title: 'Amazon.com: Example product', 88 body_text: 'Hello, zejia-wu Reviews', 89 }, 90 { 91 href: 'https://www.amazon.com/dp/B09HKN2ZRT', 92 average_rating_text: '4.4 out of 5', 93 total_review_count_text: '349 global ratings', 94 review_samples: [ 95 { 96 title: '5.0 out of 5 stars Perfect for the office', 97 rating_text: '5.0 out of 5 stars', 98 author: 'Ken', 99 date_text: 'Reviewed in the United States on March 19, 2026', 100 body: 'Good for the office, no complaints.', 101 verified: true, 102 }, 103 ], 104 }, 105 ]); 106 107 const result = await command.func(page, { input: 'B09HKN2ZRT', limit: 1 }); 108 109 expect(page.goto.mock.calls.map((call) => call[0])).toEqual([ 110 'https://www.amazon.com/product-reviews/B09HKN2ZRT', 111 'https://www.amazon.com/dp/B09HKN2ZRT', 112 ]); 113 expect(result).toEqual([ 114 expect.objectContaining({ 115 asin: 'B09HKN2ZRT', 116 discussion_url: 'https://www.amazon.com/dp/B09HKN2ZRT', 117 average_rating_value: 4.4, 118 total_review_count: 349, 119 }), 120 ]); 121 }); 122 123 it('throws AuthRequiredError when both review and product pages are gated', async () => { 124 const command = getRegistry().get('amazon/discussion'); 125 const authState = { 126 href: 'https://www.amazon.com/ap/signin?openid.return_to=https%3A%2F%2Fwww.amazon.com%2Fproduct-reviews%2FB09HKN2ZRT', 127 title: 'Amazon Sign-In', 128 body_text: 'Sign in Create account', 129 }; 130 const page = createPageMock([ 131 authState, 132 { 133 href: authState.href, 134 average_rating_text: '', 135 total_review_count_text: '', 136 review_samples: [], 137 }, 138 authState, 139 ]); 140 141 await expect(command.func(page, { input: 'B09HKN2ZRT', limit: 1 })).rejects.toBeInstanceOf(AuthRequiredError); 142 }); 143 144 it('does not treat a public product page with sign-in copy as a gated page', () => { 145 expect(__test__.isSignInState({ 146 href: 'https://www.amazon.com/dp/B09HKN2ZRT', 147 title: 'Amazon.com: Example product', 148 body_text: 'Hello, sign in Account & Lists Create account', 149 })).toBe(false); 150 }); 151 });