-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'BC-7561-batch-delete-fix' of https://github.com/hpi-sch…
…ul-cloud/schulcloud-server into BC-7561-batch-delete-fix
- Loading branch information
Showing
284 changed files
with
4,195 additions
and
2,368 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
129 changes: 129 additions & 0 deletions
129
apps/server/src/infra/files-storage-client/files-storage-client.adapter.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import { faker } from '@faker-js/faker'; | ||
import { createMock, DeepMocked } from '@golevelup/ts-jest'; | ||
import { HttpService } from '@nestjs/axios'; | ||
import { ConfigService } from '@nestjs/config'; | ||
import { REQUEST } from '@nestjs/core'; | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import { ErrorLogger, Logger } from '@core/logger'; | ||
import type { Request } from 'express'; | ||
import { from, throwError } from 'rxjs'; | ||
import { axiosResponseFactory } from '@testing/factory/axios-response.factory'; | ||
import { FilesStorageClientAdapter } from './files-storage-client.adapter'; | ||
import { FileApi } from './generated'; | ||
|
||
describe(FilesStorageClientAdapter.name, () => { | ||
let module: TestingModule; | ||
let sut: FilesStorageClientAdapter; | ||
let httpServiceMock: DeepMocked<HttpService>; | ||
let errorLoggerMock: DeepMocked<ErrorLogger>; | ||
let configServiceMock: DeepMocked<ConfigService>; | ||
|
||
beforeAll(async () => { | ||
module = await Test.createTestingModule({ | ||
providers: [ | ||
FilesStorageClientAdapter, | ||
{ | ||
provide: FileApi, | ||
useValue: createMock<FileApi>(), | ||
}, | ||
{ | ||
provide: Logger, | ||
useValue: createMock<Logger>(), | ||
}, | ||
{ | ||
provide: ErrorLogger, | ||
useValue: createMock<ErrorLogger>(), | ||
}, | ||
{ | ||
provide: HttpService, | ||
useValue: createMock<HttpService>(), | ||
}, | ||
{ | ||
provide: ConfigService, | ||
useValue: createMock<ConfigService>(), | ||
}, | ||
{ | ||
provide: REQUEST, | ||
useValue: createMock<Request>({ | ||
headers: { | ||
authorization: `Bearer ${faker.string.alphanumeric(42)}`, | ||
}, | ||
}), | ||
}, | ||
], | ||
}).compile(); | ||
|
||
sut = module.get(FilesStorageClientAdapter); | ||
httpServiceMock = module.get(HttpService); | ||
errorLoggerMock = module.get(ErrorLogger); | ||
configServiceMock = module.get(ConfigService); | ||
}); | ||
|
||
afterAll(async () => { | ||
await module.close(); | ||
}); | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(sut).toBeDefined(); | ||
}); | ||
|
||
describe('download', () => { | ||
describe('when download succeeds', () => { | ||
const setup = () => { | ||
const fileRecordId = faker.string.uuid(); | ||
const fileName = faker.system.fileName(); | ||
const observable = from([axiosResponseFactory.build({ data: Buffer.from('') })]); | ||
|
||
httpServiceMock.get.mockReturnValue(observable); | ||
configServiceMock.getOrThrow.mockReturnValue(faker.internet.url()); | ||
|
||
return { | ||
fileRecordId, | ||
fileName, | ||
}; | ||
}; | ||
|
||
it('should return the response buffer', async () => { | ||
const { fileRecordId, fileName } = setup(); | ||
|
||
const result = await sut.download(fileRecordId, fileName); | ||
|
||
expect(result).toEqual(Buffer.from('')); | ||
expect(httpServiceMock.get).toBeCalledWith(expect.any(String), { | ||
responseType: 'arraybuffer', | ||
headers: { | ||
Authorization: expect.any(String), | ||
}, | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when download fails', () => { | ||
const setup = () => { | ||
const fileRecordId = faker.string.uuid(); | ||
const fileName = faker.system.fileName(); | ||
const observable = throwError(() => new Error('error')); | ||
|
||
httpServiceMock.get.mockReturnValue(observable); | ||
|
||
return { | ||
fileRecordId, | ||
fileName, | ||
}; | ||
}; | ||
|
||
it('should return null', async () => { | ||
const { fileRecordId, fileName } = setup(); | ||
|
||
const result = await sut.download(fileRecordId, fileName); | ||
|
||
expect(result).toBeNull(); | ||
expect(errorLoggerMock.error).toBeCalled(); | ||
}); | ||
}); | ||
}); | ||
}); |
59 changes: 59 additions & 0 deletions
59
apps/server/src/infra/files-storage-client/files-storage-client.adapter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { HttpService } from '@nestjs/axios'; | ||
import { Inject, Injectable } from '@nestjs/common'; | ||
import { ConfigService } from '@nestjs/config'; | ||
import { REQUEST } from '@nestjs/core'; | ||
import { JwtExtractor } from '@shared/common/utils/jwt'; | ||
import { AxiosErrorLoggable } from '@core/error/loggable'; | ||
import { ErrorLogger, Logger } from '@core/logger'; | ||
import { AxiosError } from 'axios'; | ||
import type { Request } from 'express'; | ||
import { lastValueFrom } from 'rxjs'; | ||
import { FilesStorageClientConfig } from './files-storage-client.config'; | ||
import { FileApi } from './generated'; | ||
|
||
@Injectable() | ||
export class FilesStorageClientAdapter { | ||
constructor( | ||
private readonly api: FileApi, | ||
private readonly logger: Logger, | ||
private readonly errorLogger: ErrorLogger, | ||
// these should be removed when the generated client supports downloading files as arraybuffer | ||
private readonly httpService: HttpService, | ||
private readonly configService: ConfigService<FilesStorageClientConfig, true>, | ||
@Inject(REQUEST) private readonly req: Request | ||
) { | ||
this.logger.setContext(FilesStorageClientAdapter.name); | ||
} | ||
|
||
public async download(fileRecordId: string, fileName: string): Promise<Buffer | null> { | ||
try { | ||
// INFO: we need to download the file from the files storage service without using the generated client, | ||
// because the generated client does not support downloading files as arraybuffer. Otherwise files with | ||
// binary content would be corrupted like pdfs, zip files, etc. Setting the responseType to 'arraybuffer' | ||
// will not work with the generated client. | ||
// const response = await this.api.download(fileRecordId, fileName, undefined, { | ||
// responseType: 'arraybuffer', | ||
// }); | ||
const token = JwtExtractor.extractJwtFromRequest(this.req); | ||
const url = new URL( | ||
`${this.configService.getOrThrow<string>( | ||
'FILES_STORAGE__SERVICE_BASE_URL' | ||
)}/api/v3/file/download/${fileRecordId}/${fileName}` | ||
); | ||
const observable = this.httpService.get(url.toString(), { | ||
responseType: 'arraybuffer', | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
}); | ||
const response = await lastValueFrom(observable); | ||
const data = Buffer.isBuffer(response.data) ? response.data : null; | ||
|
||
return data; | ||
} catch (error: unknown) { | ||
this.errorLogger.error(new AxiosErrorLoggable(error as AxiosError, 'FilesStorageClientAdapter.download')); | ||
|
||
return null; | ||
} | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
apps/server/src/infra/files-storage-client/files-storage-client.config.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export interface FilesStorageClientConfig { | ||
FILES_STORAGE__SERVICE_BASE_URL: string; | ||
} |
Oops, something went wrong.