/ clis / douyin / _shared / tos-upload-short-read.test.js
tos-upload-short-read.test.js
 1  /**
 2   * Tests for the fs.readSync short-read guard in tosUpload.
 3   *
 4   * This file is separate from tos-upload.test.ts because vi.mock is hoisted and
 5   * would interfere with the real-fs tests there.
 6   *
 7   * Strategy:
 8   * - Use setReadSyncOverride (exported testing seam) to force readSync to return 0
 9   * - Mock global fetch to satisfy initMultipartUpload so the code path reaches readSync
10   */
11  import * as actualFs from 'node:fs';
12  import * as os from 'node:os';
13  import * as path from 'node:path';
14  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
15  import { CommandExecutionError } from '@jackwener/opencli/errors';
16  import { setReadSyncOverride, tosUpload } from './tos-upload.js';
17  /** Build a minimal fetch mock that satisfies initMultipartUpload (POST ?uploads → 200 + UploadId XML). */
18  function makeFetchMock() {
19      return vi.fn().mockResolvedValue({
20          status: 200,
21          text: async () => '<InitiateMultipartUploadResult><UploadId>mock-upload-id</UploadId></InitiateMultipartUploadResult>',
22          headers: { forEach: (_cb) => { } },
23      });
24  }
25  describe('tosUpload short-read guard', () => {
26      let tmpDir;
27      let tmpFile;
28      let originalFetch;
29      beforeEach(() => {
30          originalFetch = globalThis.fetch;
31          tmpDir = actualFs.mkdtempSync(path.join(os.tmpdir(), 'tos-upload-shortread-'));
32          tmpFile = path.join(tmpDir, 'video.mp4');
33          // 100-byte file — fits in a single part
34          actualFs.writeFileSync(tmpFile, Buffer.alloc(100, 0xff));
35      });
36      afterEach(() => {
37          setReadSyncOverride(null);
38          globalThis.fetch = originalFetch;
39          actualFs.rmSync(tmpDir, { recursive: true, force: true });
40      });
41      it('throws CommandExecutionError on short read', async () => {
42          // Mock fetch so initMultipartUpload succeeds and code reaches readSync
43          globalThis.fetch = makeFetchMock();
44          // Override readSync to return 0 (fewer bytes than requested)
45          setReadSyncOverride(() => 0);
46          const mockCredentials = {
47              access_key_id: 'AKIAIOSFODNN7EXAMPLE',
48              secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
49              session_token: 'test-session-token',
50              expired_time: Date.now() / 1000 + 3600,
51          };
52          const uploadInfo = {
53              tos_upload_url: 'https://tos-cn-i-alisg.volces.com/bucket/key',
54              auth: 'AWS4-HMAC-SHA256 Credential=test',
55              video_id: 'test-video-id',
56          };
57          await expect(tosUpload({
58              filePath: tmpFile,
59              uploadInfo,
60              credentials: mockCredentials,
61          })).rejects.toThrow(CommandExecutionError);
62      });
63      it('error message identifies the part number and byte counts', async () => {
64          globalThis.fetch = makeFetchMock();
65          setReadSyncOverride(() => 0);
66          const mockCredentials = {
67              access_key_id: 'AKIAIOSFODNN7EXAMPLE',
68              secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
69              session_token: 'test-session-token',
70              expired_time: Date.now() / 1000 + 3600,
71          };
72          const uploadInfo = {
73              tos_upload_url: 'https://tos-cn-i-alisg.volces.com/bucket/key',
74              auth: 'AWS4-HMAC-SHA256 Credential=test',
75              video_id: 'test-video-id',
76          };
77          await expect(tosUpload({
78              filePath: tmpFile,
79              uploadInfo,
80              credentials: mockCredentials,
81          })).rejects.toThrow(/Short read on part 1: expected 100 bytes, got 0/);
82      });
83  });