Преглед изворни кода

Merge pull request #8849 from weseek/feat/135775-143630-page-bulk-export-service-test

Feat/135775 143630 page bulk export service test
Yuki Takei пре 1 година
родитељ
комит
1b562b1f17

+ 1 - 8
apps/app/src/server/service/file-uploader/aws/multipart-uploader.ts

@@ -5,18 +5,11 @@ import {
 
 import loggerFactory from '~/utils/logger';
 
-import { MultipartUploader, type IMultipartUploader } from '../multipart-uploader';
+import { MultipartUploader, UploadStatus, type IMultipartUploader } from '../multipart-uploader';
 
 
 const logger = loggerFactory('growi:services:fileUploaderAws:multipartUploader');
 
-enum UploadStatus {
-  BEFORE_INIT,
-  IN_PROGRESS,
-  COMPLETED,
-  ABORTED
-}
-
 export type IAwsMultipartUploader = IMultipartUploader
 
 /**

+ 1 - 8
apps/app/src/server/service/file-uploader/gcs/multipart-uploader.ts

@@ -4,17 +4,10 @@ import axios from 'axios';
 
 import loggerFactory from '~/utils/logger';
 
-import { MultipartUploader, type IMultipartUploader } from '../multipart-uploader';
+import { MultipartUploader, UploadStatus, type IMultipartUploader } from '../multipart-uploader';
 
 const logger = loggerFactory('growi:services:fileUploaderGcs:multipartUploader');
 
-enum UploadStatus {
-  BEFORE_INIT,
-  IN_PROGRESS,
-  COMPLETED,
-  ABORTED
-}
-
 export type IGcsMultipartUploader = IMultipartUploader
 
 /**

+ 68 - 0
apps/app/src/server/service/file-uploader/multipart-uploader.spec.ts

@@ -0,0 +1,68 @@
+import { UploadStatus, MultipartUploader } from './multipart-uploader';
+
+class MockMultipartUploader extends MultipartUploader {
+
+  async initUpload(): Promise<void> { return }
+
+  async uploadPart(part: Buffer, partNumber: number): Promise<void> { return }
+
+  async completeUpload(): Promise<void> { return }
+
+  async abortUpload(): Promise<void> { return }
+
+  async getUploadedFileSize(): Promise<number> { return 0 }
+
+  // Expose the protected method for testing
+  testValidateUploadStatus(desired: UploadStatus): void {
+    this.validateUploadStatus(desired);
+  }
+
+  setCurrentStatus(status: UploadStatus): void {
+    this.currentStatus = status;
+  }
+
+}
+
+describe('MultipartUploader', () => {
+  let uploader: MockMultipartUploader;
+
+  beforeEach(() => {
+    uploader = new MockMultipartUploader('test-upload-key', 10485760);
+  });
+
+  describe('validateUploadStatus', () => {
+    describe('When current status is equal to desired status', () => {
+      it('should not throw error', () => {
+        uploader.setCurrentStatus(UploadStatus.ABORTED);
+        expect(() => uploader.testValidateUploadStatus(UploadStatus.ABORTED)).not.toThrow();
+      });
+    });
+
+    describe('When current status is not equal to desired status', () => {
+      const cases = [
+        { current: UploadStatus.BEFORE_INIT, desired: UploadStatus.IN_PROGRESS, errorMessage: 'Multipart upload has not been initiated' },
+        { current: UploadStatus.BEFORE_INIT, desired: UploadStatus.COMPLETED, errorMessage: 'Multipart upload has not been initiated' },
+        { current: UploadStatus.BEFORE_INIT, desired: UploadStatus.ABORTED, errorMessage: 'Multipart upload has not been initiated' },
+        { current: UploadStatus.IN_PROGRESS, desired: UploadStatus.BEFORE_INIT, errorMessage: 'Multipart upload is already in progress' },
+        { current: UploadStatus.IN_PROGRESS, desired: UploadStatus.COMPLETED, errorMessage: 'Multipart upload is still in progress' },
+        { current: UploadStatus.IN_PROGRESS, desired: UploadStatus.ABORTED, errorMessage: 'Multipart upload is still in progress' },
+        { current: UploadStatus.COMPLETED, desired: UploadStatus.BEFORE_INIT, errorMessage: 'Multipart upload has already been completed' },
+        { current: UploadStatus.COMPLETED, desired: UploadStatus.IN_PROGRESS, errorMessage: 'Multipart upload has already been completed' },
+        { current: UploadStatus.COMPLETED, desired: UploadStatus.ABORTED, errorMessage: 'Multipart upload has already been completed' },
+        { current: UploadStatus.ABORTED, desired: UploadStatus.BEFORE_INIT, errorMessage: 'Multipart upload has been aborted' },
+        { current: UploadStatus.ABORTED, desired: UploadStatus.IN_PROGRESS, errorMessage: 'Multipart upload has been aborted' },
+        { current: UploadStatus.ABORTED, desired: UploadStatus.COMPLETED, errorMessage: 'Multipart upload has been aborted' },
+      ];
+
+      describe.each(cases)('When current status is $current and desired status is $desired', ({ current, desired, errorMessage }) => {
+        beforeEach(() => {
+          uploader.setCurrentStatus(current);
+        });
+
+        it(`should throw expected error: "${errorMessage}"`, () => {
+          expect(() => uploader.testValidateUploadStatus(desired)).toThrow(errorMessage);
+        });
+      });
+    });
+  });
+});

+ 10 - 7
apps/app/src/server/service/file-uploader/multipart-uploader.ts

@@ -2,7 +2,7 @@ import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:services:fileUploader:multipartUploader');
 
-enum UploadStatus {
+export enum UploadStatus {
   BEFORE_INIT,
   IN_PROGRESS,
   COMPLETED,
@@ -67,14 +67,17 @@ export abstract class MultipartUploader implements IMultipartUploader {
       errMsg = 'Multipart upload has been aborted';
     }
 
-    // currentStatus is IN_PROGRESS or BEFORE_INIT
-
-    if (this.currentStatus === UploadStatus.IN_PROGRESS && desiredStatus === UploadStatus.BEFORE_INIT) {
-      errMsg = 'Multipart upload has already been initiated';
+    if (this.currentStatus === UploadStatus.IN_PROGRESS) {
+      if (desiredStatus === UploadStatus.BEFORE_INIT) {
+        errMsg = 'Multipart upload is already in progress';
+      }
+      else {
+        errMsg = 'Multipart upload is still in progress';
+      }
     }
 
-    if (this.currentStatus === UploadStatus.BEFORE_INIT && desiredStatus === UploadStatus.IN_PROGRESS) {
-      errMsg = 'Multipart upload not initiated';
+    if (this.currentStatus === UploadStatus.BEFORE_INIT) {
+      errMsg = 'Multipart upload has not been initiated';
     }
 
     if (errMsg != null) {

+ 33 - 0
apps/app/src/server/util/stream.spec.ts

@@ -0,0 +1,33 @@
+import { Readable, Writable, pipeline } from 'stream';
+import { promisify } from 'util';
+
+import { getBufferToFixedSizeTransform } from './stream';
+
+const pipelinePromise = promisify(pipeline);
+
+describe('stream util', () => {
+  describe('getBufferToFixedSizeTransform', () => {
+    it('should buffer data to fixed size and push to next stream', async() => {
+      const bufferSize = 10;
+      const chunks: Buffer[] = [];
+
+      const readable = Readable.from([Buffer.from('1234567890A'), Buffer.from('BCDE'), Buffer.from('FGH'), Buffer.from('IJKL')]);
+      const transform = getBufferToFixedSizeTransform(bufferSize);
+      const writable = new Writable({
+        write(chunk: Buffer, encoding, callback) {
+          chunks.push(chunk);
+          callback();
+        },
+      });
+
+      const expectedChunks = [Buffer.from('1234567890'), Buffer.from('ABCDEFGHIJ'), Buffer.from('KL')];
+
+      await pipelinePromise(readable, transform, writable);
+
+      expect(chunks).toHaveLength(expectedChunks.length);
+      expectedChunks.forEach((expectedChunk, index) => {
+        expect(chunks[index].toString()).toBe(expectedChunk.toString());
+      });
+    });
+  });
+});