/ clis / paperreview / commands.test.js
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  });