Skip to content

Commit

Permalink
Merge branch 'main' into BC-5629-batch-deletion-mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
bn-pass authored Nov 9, 2023
2 parents b12527c + a7273a1 commit d17c097
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { AntivirusService } from '@infra/antivirus';
import { PreviewProducer } from '@infra/preview-generator';
import { S3ClientAdapter } from '@infra/s3-client';
import { EntityManager, ObjectId } from '@mikro-orm/mongodb';
import { ICurrentUser } from '@modules/authentication';
import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard';
import { ExecutionContext, INestApplication, NotFoundException, StreamableFile } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { ApiValidationError } from '@shared/common';
import { EntityId, Permission } from '@shared/domain';
import { AntivirusService } from '@infra/antivirus';
import { PreviewProducer } from '@infra/preview-generator';
import { S3ClientAdapter } from '@infra/s3-client';
import { cleanupCollections, mapUserToCurrentUser, roleFactory, schoolFactory, userFactory } from '@shared/testing';
import NodeClam from 'clamscan';
import { Request } from 'express';
Expand Down Expand Up @@ -63,6 +63,19 @@ class API {
};
}

async getPreviewWithEtag(routeName: string, etag: string, query?: string | Record<string, unknown>) {
const response = await request(this.app.getHttpServer())
.get(routeName)
.query(query || {})
.set('If-None-Match', etag);

return {
result: response.body as StreamableFile,
error: response.body as ApiValidationError,
status: response.status,
};
}

async getPreviewBytesRange(routeName: string, bytesRange: string, query?: string | Record<string, unknown>) {
const response = await request(this.app.getHttpServer())
.get(routeName)
Expand Down Expand Up @@ -299,34 +312,75 @@ describe('File Controller (API) - preview', () => {
return { uploadedFile };
};

it('should return status 200 for successful download', async () => {
const { uploadedFile } = await setup();
const query = {
...defaultQueryParameters,
forceUpdate: false,
};

const response = await api.getPreview(`/file/preview/${uploadedFile.id}/${uploadedFile.name}`, query);

expect(response.status).toEqual(200);
describe('WHEN header contains no etag', () => {
it('should return status 200 for successful download', async () => {
const { uploadedFile } = await setup();
const query = {
...defaultQueryParameters,
forceUpdate: false,
};

const response = await api.getPreview(`/file/preview/${uploadedFile.id}/${uploadedFile.name}`, query);

expect(response.status).toEqual(200);
});

it('should return status 206 and required headers for the successful partial file stream download', async () => {
const { uploadedFile } = await setup();
const query = {
...defaultQueryParameters,
forceUpdate: false,
};

const response = await api.getPreviewBytesRange(
`/file/preview/${uploadedFile.id}/${uploadedFile.name}`,
'bytes=0-',
query
);

expect(response.status).toEqual(206);
expect(response.headers['accept-ranges']).toMatch('bytes');
expect(response.headers['content-range']).toMatch('bytes 0-3/4');
expect(response.headers.etag).toMatch('testTag');
});
});

it('should return status 206 and required headers for the successful partial file stream download', async () => {
const { uploadedFile } = await setup();
const query = {
...defaultQueryParameters,
forceUpdate: false,
};

const response = await api.getPreviewBytesRange(
`/file/preview/${uploadedFile.id}/${uploadedFile.name}`,
'bytes=0-',
query
);
describe('WHEN header contains not matching etag', () => {
it('should return status 200 for successful download', async () => {
const { uploadedFile } = await setup();
const query = {
...defaultQueryParameters,
forceUpdate: false,
};
const etag = 'otherTag';

const response = await api.getPreviewWithEtag(
`/file/preview/${uploadedFile.id}/${uploadedFile.name}`,
etag,
query
);

expect(response.status).toEqual(200);
});
});

expect(response.status).toEqual(206);
expect(response.headers['accept-ranges']).toMatch('bytes');
expect(response.headers['content-range']).toMatch('bytes 0-3/4');
describe('WHEN header contains matching etag', () => {
it('should return status 304', async () => {
const { uploadedFile } = await setup();
const query = {
...defaultQueryParameters,
forceUpdate: false,
};
const etag = 'testTag';

const response = await api.getPreviewWithEtag(
`/file/preview/${uploadedFile.id}/${uploadedFile.name}`,
etag,
query
);

expect(response.status).toEqual(304);
});
});
});

Expand Down Expand Up @@ -369,6 +423,7 @@ describe('File Controller (API) - preview', () => {
expect(response.status).toEqual(206);
expect(response.headers['accept-ranges']).toMatch('bytes');
expect(response.headers['content-range']).toMatch('bytes 0-3/4');
expect(response.headers.etag).toMatch('testTag');
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export class FilesStorageController {
@ApiOperation({ summary: 'Streamable download of a preview file.' })
@ApiResponse({ status: 200, type: StreamableFile })
@ApiResponse({ status: 206, type: StreamableFile })
@ApiResponse({ status: 304, description: 'Not Modified' })
@ApiResponse({ status: 400, type: ApiValidationError })
@ApiResponse({ status: 403, type: ForbiddenException })
@ApiResponse({ status: 404, type: NotFoundException })
Expand All @@ -134,15 +135,24 @@ export class FilesStorageController {
@Query() previewParams: PreviewParams,
@Req() req: Request,
@Res({ passthrough: true }) response: Response,
@Headers('Range') bytesRange?: string
): Promise<StreamableFile> {
@Headers('Range') bytesRange?: string,
@Headers('If-None-Match') etag?: string
): Promise<StreamableFile | void> {
const fileResponse = await this.filesStorageUC.downloadPreview(
currentUser.userId,
params,
previewParams,
bytesRange
);

response.set({ ETag: fileResponse.etag });

if (etag === fileResponse.etag) {
response.status(HttpStatus.NOT_MODIFIED);

return undefined;
}

const streamableFile = this.streamFileToClient(req, fileResponse, response, bytesRange);

return streamableFile;
Expand Down

0 comments on commit d17c097

Please sign in to comment.