commands.test.js
1 import { beforeEach, describe, expect, it, vi } from 'vitest'; 2 const { mockReadPdfFile, mockRequestJson, mockUploadPresignedPdf, mockValidateHelpfulness, mockParseYesNo, } = vi.hoisted(() => ({ 3 mockReadPdfFile: vi.fn(), 4 mockRequestJson: vi.fn(), 5 mockUploadPresignedPdf: vi.fn(), 6 mockValidateHelpfulness: vi.fn(), 7 mockParseYesNo: vi.fn(), 8 })); 9 vi.mock('./utils.js', async () => { 10 const actual = await vi.importActual('./utils.js'); 11 return { 12 ...actual, 13 readPdfFile: mockReadPdfFile, 14 requestJson: mockRequestJson, 15 uploadPresignedPdf: mockUploadPresignedPdf, 16 validateHelpfulness: mockValidateHelpfulness, 17 parseYesNo: mockParseYesNo, 18 }; 19 }); 20 import { getRegistry } from '@jackwener/opencli/registry'; 21 import './submit.js'; 22 import './review.js'; 23 import './feedback.js'; 24 describe('paperreview submit command', () => { 25 beforeEach(() => { 26 mockReadPdfFile.mockReset(); 27 mockRequestJson.mockReset(); 28 mockUploadPresignedPdf.mockReset(); 29 mockValidateHelpfulness.mockReset(); 30 mockParseYesNo.mockReset(); 31 }); 32 it('supports dry run without any remote request', async () => { 33 const cmd = getRegistry().get('paperreview/submit'); 34 expect(cmd?.func).toBeTypeOf('function'); 35 mockReadPdfFile.mockResolvedValue({ 36 buffer: Buffer.from('%PDF'), 37 fileName: 'paper.pdf', 38 resolvedPath: '/tmp/paper.pdf', 39 sizeBytes: 4096, 40 }); 41 const result = await cmd.func(null, { 42 pdf: './paper.pdf', 43 email: 'wang2629651228@gmail.com', 44 venue: 'RAL', 45 'dry-run': true, 46 'prepare-only': false, 47 }); 48 expect(mockRequestJson).not.toHaveBeenCalled(); 49 expect(result).toMatchObject({ 50 status: 'dry-run', 51 file: 'paper.pdf', 52 email: 'wang2629651228@gmail.com', 53 venue: 'RAL', 54 }); 55 }); 56 it('treats explicit false flags as false and performs the real submission path', async () => { 57 const cmd = getRegistry().get('paperreview/submit'); 58 expect(cmd?.func).toBeTypeOf('function'); 59 mockReadPdfFile.mockResolvedValue({ 60 buffer: Buffer.from('%PDF'), 61 fileName: 'paper.pdf', 62 resolvedPath: '/tmp/paper.pdf', 63 sizeBytes: 4096, 64 }); 65 mockRequestJson 66 .mockResolvedValueOnce({ 67 response: { ok: true, status: 200 }, 68 payload: { 69 success: true, 70 presigned_url: 'https://upload.example.com', 71 presigned_fields: { key: 'uploads/paper.pdf' }, 72 s3_key: 'uploads/paper.pdf', 73 }, 74 }) 75 .mockResolvedValueOnce({ 76 response: { ok: true, status: 200 }, 77 payload: { 78 success: true, 79 token: 'tok_false', 80 message: 'Submission accepted', 81 }, 82 }); 83 const result = await cmd.func(null, { 84 pdf: './paper.pdf', 85 email: 'wang2629651228@gmail.com', 86 venue: 'RAL', 87 'dry-run': false, 88 'prepare-only': false, 89 }); 90 expect(mockUploadPresignedPdf).toHaveBeenCalledTimes(1); 91 expect(result).toMatchObject({ 92 status: 'submitted', 93 token: 'tok_false', 94 review_url: 'https://paperreview.ai/review?token=tok_false', 95 }); 96 }); 97 it('supports prepare-only without uploading the PDF', async () => { 98 const cmd = getRegistry().get('paperreview/submit'); 99 expect(cmd?.func).toBeTypeOf('function'); 100 mockReadPdfFile.mockResolvedValue({ 101 buffer: Buffer.from('%PDF'), 102 fileName: 'paper.pdf', 103 resolvedPath: '/tmp/paper.pdf', 104 sizeBytes: 4096, 105 }); 106 mockRequestJson.mockResolvedValueOnce({ 107 response: { ok: true, status: 200 }, 108 payload: { 109 success: true, 110 presigned_url: 'https://upload.example.com', 111 presigned_fields: { key: 'uploads/paper.pdf' }, 112 s3_key: 'uploads/paper.pdf', 113 }, 114 }); 115 const result = await cmd.func(null, { 116 pdf: './paper.pdf', 117 email: 'wang2629651228@gmail.com', 118 venue: 'RAL', 119 'dry-run': false, 120 'prepare-only': true, 121 }); 122 expect(mockUploadPresignedPdf).not.toHaveBeenCalled(); 123 expect(mockRequestJson).toHaveBeenCalledTimes(1); 124 expect(result).toMatchObject({ 125 status: 'prepared', 126 s3_key: 'uploads/paper.pdf', 127 }); 128 }); 129 it('requests an upload URL, uploads the PDF, and confirms the submission', async () => { 130 const cmd = getRegistry().get('paperreview/submit'); 131 expect(cmd?.func).toBeTypeOf('function'); 132 mockReadPdfFile.mockResolvedValue({ 133 buffer: Buffer.from('%PDF'), 134 fileName: 'paper.pdf', 135 resolvedPath: '/tmp/paper.pdf', 136 sizeBytes: 4096, 137 }); 138 mockRequestJson 139 .mockResolvedValueOnce({ 140 response: { ok: true, status: 200 }, 141 payload: { 142 success: true, 143 presigned_url: 'https://upload.example.com', 144 presigned_fields: { key: 'uploads/paper.pdf' }, 145 s3_key: 'uploads/paper.pdf', 146 }, 147 }) 148 .mockResolvedValueOnce({ 149 response: { ok: true, status: 200 }, 150 payload: { 151 success: true, 152 token: 'tok_123', 153 message: 'Submission accepted', 154 }, 155 }); 156 const result = await cmd.func(null, { 157 pdf: './paper.pdf', 158 email: 'wang2629651228@gmail.com', 159 venue: 'RAL', 160 'dry-run': false, 161 'prepare-only': false, 162 }); 163 expect(mockRequestJson).toHaveBeenNthCalledWith(1, '/api/get-upload-url', expect.objectContaining({ 164 method: 'POST', 165 body: JSON.stringify({ 166 filename: 'paper.pdf', 167 venue: 'RAL', 168 }), 169 })); 170 expect(mockUploadPresignedPdf).toHaveBeenCalledWith('https://upload.example.com', expect.objectContaining({ fileName: 'paper.pdf' }), expect.objectContaining({ s3_key: 'uploads/paper.pdf' })); 171 expect(mockRequestJson).toHaveBeenNthCalledWith(2, '/api/confirm-upload', expect.objectContaining({ 172 method: 'POST', 173 body: expect.any(FormData), 174 })); 175 expect(result).toMatchObject({ 176 status: 'submitted', 177 token: 'tok_123', 178 review_url: 'https://paperreview.ai/review?token=tok_123', 179 }); 180 }); 181 }); 182 describe('paperreview review command', () => { 183 beforeEach(() => { 184 mockRequestJson.mockReset(); 185 }); 186 it('returns processing status when the review is not ready yet', async () => { 187 const cmd = getRegistry().get('paperreview/review'); 188 expect(cmd?.func).toBeTypeOf('function'); 189 mockRequestJson.mockResolvedValue({ 190 response: { status: 202 }, 191 payload: { detail: 'Review is still processing.' }, 192 }); 193 const result = await cmd.func(null, { token: 'tok_123' }); 194 expect(result).toMatchObject({ 195 status: 'processing', 196 token: 'tok_123', 197 review_url: 'https://paperreview.ai/review?token=tok_123', 198 message: 'Review is still processing.', 199 }); 200 }); 201 }); 202 describe('paperreview feedback command', () => { 203 beforeEach(() => { 204 mockRequestJson.mockReset(); 205 mockValidateHelpfulness.mockReset(); 206 mockParseYesNo.mockReset(); 207 }); 208 it('normalizes feedback inputs and posts them to the API', async () => { 209 const cmd = getRegistry().get('paperreview/feedback'); 210 expect(cmd?.func).toBeTypeOf('function'); 211 mockValidateHelpfulness.mockReturnValue(4); 212 mockParseYesNo.mockReturnValueOnce(true).mockReturnValueOnce(false); 213 mockRequestJson.mockResolvedValue({ 214 response: { ok: true, status: 200 }, 215 payload: { message: 'Thanks for the feedback.' }, 216 }); 217 const result = await cmd.func(null, { 218 token: 'tok_123', 219 helpfulness: 4, 220 'critical-error': 'yes', 221 'actionable-suggestions': 'no', 222 'additional-comments': 'Helpful summary, but the contribution section needs more detail.', 223 }); 224 expect(mockRequestJson).toHaveBeenCalledWith('/api/feedback/tok_123', { 225 method: 'POST', 226 headers: { 'Content-Type': 'application/json' }, 227 body: JSON.stringify({ 228 helpfulness: 4, 229 has_critical_error: true, 230 has_actionable_suggestions: false, 231 additional_comments: 'Helpful summary, but the contribution section needs more detail.', 232 }), 233 }); 234 expect(result).toMatchObject({ 235 status: 'submitted', 236 token: 'tok_123', 237 helpfulness: 4, 238 critical_error: true, 239 actionable_suggestions: false, 240 message: 'Thanks for the feedback.', 241 }); 242 }); 243 });