From ff7fbcaf0e07ad6dd25bc78a6b1c0a756eb4e812 Mon Sep 17 00:00:00 2001 From: Philip Molares Date: Thu, 25 Mar 2021 21:20:56 +0100 Subject: [PATCH 1/3] PrivateAPI: Add media controller Signed-off-by: Philip Molares --- .../private/media/media.controller.spec.ts | 86 +++++++++++++++++++ src/api/private/media/media.controller.ts | 69 +++++++++++++++ src/api/private/private-api.module.ts | 4 +- 3 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 src/api/private/media/media.controller.spec.ts create mode 100644 src/api/private/media/media.controller.ts diff --git a/src/api/private/media/media.controller.spec.ts b/src/api/private/media/media.controller.spec.ts new file mode 100644 index 000000000..61e60dbf7 --- /dev/null +++ b/src/api/private/media/media.controller.spec.ts @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Test, TestingModule } from '@nestjs/testing'; +import { MediaController } from './media.controller'; +import { LoggerModule } from '../../../logger/logger.module'; +import { ConfigModule } from '@nestjs/config'; +import mediaConfigMock from '../../../config/mock/media.config.mock'; +import appConfigMock from '../../../config/mock/app.config.mock'; +import authConfigMock from '../../../config/mock/auth.config.mock'; +import customizationConfigMock from '../../../config/mock/customization.config.mock'; +import externalConfigMock from '../../../config/mock/external-services.config.mock'; +import { MediaModule } from '../../../media/media.module'; +import { NotesModule } from '../../../notes/notes.module'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { AuthorColor } from '../../../notes/author-color.entity'; +import { Authorship } from '../../../revisions/authorship.entity'; +import { AuthToken } from '../../../auth/auth-token.entity'; +import { Identity } from '../../../users/identity.entity'; +import { MediaUpload } from '../../../media/media-upload.entity'; +import { Note } from '../../../notes/note.entity'; +import { Revision } from '../../../revisions/revision.entity'; +import { User } from '../../../users/user.entity'; +import { Tag } from '../../../notes/tag.entity'; +import { NoteGroupPermission } from '../../../permissions/note-group-permission.entity'; +import { NoteUserPermission } from '../../../permissions/note-user-permission.entity'; +import { Group } from '../../../groups/group.entity'; + +describe('MediaController', () => { + let controller: MediaController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + MediaModule, + NotesModule, + LoggerModule, + ConfigModule.forRoot({ + isGlobal: true, + load: [ + appConfigMock, + mediaConfigMock, + authConfigMock, + customizationConfigMock, + externalConfigMock, + ], + }), + ], + controllers: [MediaController], + }) + .overrideProvider(getRepositoryToken(AuthorColor)) + .useValue({}) + .overrideProvider(getRepositoryToken(Authorship)) + .useValue({}) + .overrideProvider(getRepositoryToken(AuthToken)) + .useValue({}) + .overrideProvider(getRepositoryToken(Identity)) + .useValue({}) + .overrideProvider(getRepositoryToken(MediaUpload)) + .useValue({}) + .overrideProvider(getRepositoryToken(Note)) + .useValue({}) + .overrideProvider(getRepositoryToken(Revision)) + .useValue({}) + .overrideProvider(getRepositoryToken(User)) + .useValue({}) + .overrideProvider(getRepositoryToken(Tag)) + .useValue({}) + .overrideProvider(getRepositoryToken(NoteGroupPermission)) + .useValue({}) + .overrideProvider(getRepositoryToken(NoteUserPermission)) + .useValue({}) + .overrideProvider(getRepositoryToken(Group)) + .useValue({}) + .compile(); + + controller = module.get(MediaController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/api/private/media/media.controller.ts b/src/api/private/media/media.controller.ts new file mode 100644 index 000000000..7b5fde80a --- /dev/null +++ b/src/api/private/media/media.controller.ts @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { + BadRequestException, + Controller, + Headers, + HttpCode, + InternalServerErrorException, + Post, + UploadedFile, + UseInterceptors, +} from '@nestjs/common'; +import { ConsoleLoggerService } from '../../../logger/console-logger.service'; +import { MediaService } from '../../../media/media.service'; +import { MulterFile } from '../../../media/multer-file.interface'; +import { MediaUploadUrlDto } from '../../../media/media-upload-url.dto'; +import { + ClientError, + MediaBackendError, + NotInDBError, +} from '../../../errors/errors'; +import { FileInterceptor } from '@nestjs/platform-express'; + +@Controller('media') +export class MediaController { + constructor( + private readonly logger: ConsoleLoggerService, + private mediaService: MediaService, + ) { + this.logger.setContext(MediaController.name); + } + + @Post() + @UseInterceptors(FileInterceptor('file')) + @HttpCode(201) + async uploadMedia( + @UploadedFile() file: MulterFile, + @Headers('HedgeDoc-Note') noteId: string, + ): Promise { + // ToDo: Get real userName + const username = 'hardcoded'; + this.logger.debug( + `Recieved filename '${file.originalname}' for note '${noteId}' from user '${username}'`, + 'uploadImage', + ); + try { + const url = await this.mediaService.saveFile( + file.buffer, + username, + noteId, + ); + return this.mediaService.toMediaUploadUrlDto(url); + } catch (e) { + if (e instanceof ClientError || e instanceof NotInDBError) { + throw new BadRequestException(e.message); + } + if (e instanceof MediaBackendError) { + throw new InternalServerErrorException( + 'There was an error in the media backend', + ); + } + throw e; + } + } +} diff --git a/src/api/private/private-api.module.ts b/src/api/private/private-api.module.ts index bd51ad1e5..729c65306 100644 --- a/src/api/private/private-api.module.ts +++ b/src/api/private/private-api.module.ts @@ -14,9 +14,10 @@ import { FrontendConfigModule } from '../../frontend-config/frontend-config.modu import { HistoryController } from './me/history/history.controller'; import { HistoryModule } from '../../history/history.module'; import { NotesModule } from '../../notes/notes.module'; +import { MediaModule } from '../../media/media.module'; +import { MediaController } from './media/media.controller'; import { NotesController } from './notes/notes.controller'; import { PermissionsModule } from '../../permissions/permissions.module'; -import { MediaModule } from '../../media/media.module'; import { RevisionsModule } from '../../revisions/revisions.module'; @Module({ @@ -34,6 +35,7 @@ import { RevisionsModule } from '../../revisions/revisions.module'; controllers: [ TokensController, ConfigController, + MediaController, HistoryController, NotesController, ], From 406c8d620a736129ec1441bdbce3d2fd4388f408 Mon Sep 17 00:00:00 2001 From: Philip Molares Date: Wed, 24 Mar 2021 11:14:48 +0100 Subject: [PATCH 2/3] PrivateE2E: Add test fixtures for private api Signed-off-by: Philip Molares --- test/private-api/fixtures/test.png | Bin 0 -> 7904 bytes test/private-api/fixtures/test.png.license | 3 +++ test/private-api/fixtures/test.zip | Bin 0 -> 186 bytes test/private-api/fixtures/test.zip.license | 3 +++ 4 files changed, 6 insertions(+) create mode 100644 test/private-api/fixtures/test.png create mode 100644 test/private-api/fixtures/test.png.license create mode 100644 test/private-api/fixtures/test.zip create mode 100644 test/private-api/fixtures/test.zip.license diff --git a/test/private-api/fixtures/test.png b/test/private-api/fixtures/test.png new file mode 100644 index 0000000000000000000000000000000000000000..5f04fc47282983bc528f8ea8d3c0e8b5c2c6be58 GIT binary patch literal 7904 zcma)BcQhN&zfY@1tEH-_)}}_)ijgK(?M-cJ)v8hiNsL&vs%EO%5}_#75~HZST51!q zgNmw=h^=C)=lA}5@0@qeJLk^N{eJJgpL@UGbM8oZWUO=T%Izxv0N|RQuBI6PaAEg9 zk>MNwY_z0DzDN04PrY z07xbPz~PtOXsUGHaM8s;M-y-sj~Zw@uhGNxjI`(%uduSpvh)@iFarSGWIatai{R0{<;_zWaK7{ON9Q1_su7PglB+a6+3c4f`#jbBd%P5cCWO z-fx}lc+u`N|HF$H45>Z5oMvEt(;3p4W~mj)Jo(V@HAO@PX#r8SiQfX=;5RgQJ8^u zPfpb8544EYS}#fsS%4adz|k(GXcOkcIH|kkq@LdN=2+jzuO|N*w%rI-oguP1ME{z2 zyiam^g~ZIH_o109(m42=Nd64VkHbx0Md-{(I{n#iW8Qqb+a$-wenQ@VA;F=u53sEH zb~neR+Cp{TjN`J8u(()BmF_d8Hq8>m2JjHH5-TZvQcZfNU;lXasnTpJzVqkFRA_tE zM%S9Hst|m`{{$TuQEV;k{44i}71GTx6h8mrA{HGoC*^uM$Ak+RjChpBmB{_qCH3UF z*je#n+-bG-7Z;|%=K-OEpAAux(L>E$|+{_at}%*#~zp z^_z_FGhS>C;mogNKZefI;u51amu?^pOjTTC{`&shPmVAtPpUAhSsC1ipN&K@JAI{c zsy-8dD>)j5C|^xY-f9vXG7r&l8F%>ny?5B%Q6>K@C`{g+Hc0jO*3H>h+sO}EQ;5)Fo}hRj)?gj?d~U-2mLxq z6~v3+YhZ#h%P{Kj6oGolQ}m^;6}NKOim{8_EpU^|TWx5cvf-2vJ4|20++K<&hL=g|K3PBk0^Rn<*PYO{90TSCz4VR5JD>(C(=iEkFs{T>ZDDS@5 zCdenV-Q3*0lBD!gP`KH^P1#sgZt>zaLtO%^To-F4p9k0#i~22``5D^vL8Env zGG-tb9HEZ^`fk-ifCJg_iACX$WtZ-3CfN1t)z<27WHEj-@oN_9jO6j(lCCTfZPvoO zaV%B=)007aX^ku}_SU=Yw>EoY94Am^g5Ec1!qd>;*|M%bEyVR_$8CorOk<452VwIghmqaEdek9Xu%y z3w*TcWB(s@`NfZ;ZSKlPwBL%i*(jTI#zL^1O4b(TT7#4&GAhq*Co>IqrU{(nWG<*a zcsEY&B-ha+4>^nff;(oD8C8SJXn_&Q0 z5V5_yCbxGIux2_>u5+I0SYY!owjLsFzt5XmzN-B`yNc_?I~6iDHBZ!`{TV%$A$3X~$@0!Y#)xtb8{#ng68E{E z-8(nI#8zu}2IVmhTjImkxzNB55GNS>fcE=gRiUflGMnNOzK=-WJoAnH@$GmWK7)Qn zS+ly4?6`o}oyj-iTG?j4M9OGWtg>{1SqGo0kwMPg(a(n-uyrb(Ri52?rcD#DSw>=D<-NDu zcyPX;Y=RCAf!FM1!@)bfd)TALUuoC|5R5xqfU0slQ`1&Lt0&@P8|rFP_Zx>V+2?zA zuo3ArSIAT3QF`&Eg)LE`0 zf*c>>crd^JnS-+Q0-2PiLnyC%zOP^GivfQZTF)CC=fbOA_KZ)VH_Q0J6~j=z^dj|b zQbc&vn7efRh42A6O4-M1dEni~FiLF}&7vUrzSnNfXCaLYgW0FB%sg(m3v`T}>q&n% z`GQ$S2Uoeeulu2r*RIjDn(H*6xZjz{C(XLuJNb5awfDG#!1+u}ldlbbEPpvm| zpo3j4XqMB57dGy1Fc(TDffwCwn@}h4X)8Jg!`aj2lJVg0q6;a==j5JU!53poGVv_G zhKG3Hvd=mqJO^Y_TjWjip;w;MA$sV{GP=1Whg-ewHQ&7lFMM=$KYDfiE|7k6>E&wE zDM~ zTYYVWSy^{*VK?W2=H#V3QHUzSa-dTCv`}nU-+efwSll)BhsBQi5CBw+b!D1X;4U%D z7c@s)y%l%)i)lfG-{i28NTT1&HbI0qMzj|cY&2`q+5B3gvEL?Z8rk! zw4h`19-oWFy>N`&Xkn)8^70b!OCovM5ahR5^tn*`VKxYh+7gNz74X2vX(Ns6rYdjg_Gh$6n3D= z4bOCf54jGDucw$lKIq$>?!D#(%}(RV%rFtpk3H;`IBnMUL1-vTYoLi_d)W9mMs(CD zQ@mVKauk#8U0w_MY#4L{`TGb(O|D&zyf~Hy)5j1WAZl7!texh5XB!3ug-lw`uvKim zT`3wIwOkGcR1Jq>pwvvE+HK{oh(p)P}*Zj7K~d zwm5RJ=+nw|*Nd}+1=B=ctXSmYQ}T}vc6<#=XIxIPwO0J`JjM&Lh323aUi-Up@qi&)*c@@F}&uF+u z8JxyhX=jVU7w`W0!73?ht=VF6TsH8&6+3>T@rOw>c*Oo1)fEYxkUNr@M2qM9ZrP!` z`kHpplHcuV5vgdRL4zTOOZf{(_qi8~2NpD(P;^q<8$ZX9sxYkbIqi$LQH+BZo@5V5 zNifu5k2mrNFK@xnJlN9*<~|9EgG)GS!EG3s$F-_iBXT`Yc|$J4j$3Vo{^_vPFw?z& z;)XE_gZ>B4U3&!3p}t{&qSZ*|%2;8~G$?KD=0h5^1U)kxv44`#fbS|>9@(2;o%U>v zT>smR8Qcx_3F7-U*SY*=`N(k{G(`UsBe|J47Sum**Ag^cSQnjY*fcY^98e-i27i|y z1vO0D>r+$cP=v=DJ!+m(gM4#`LLiI*0rNKqPaLv%@2N5x`fc*+6<=6G1H7Tt_BoMX zHgRWzvk;qE-)9d&6#n48YMR&K*Kh}6H{fnQm&Yv!ytm83fF!Ih2NcI&fmd?&!q+$D zj~$?r?~rMG;g^X01?o`MheYjJZ5VnijS``!Z$Y-nC+IE%i=lG%vx9k&djBcn$4^t) zPYL|M44v>Hy^frHtKsTKulO<{Sjd>QC%yabs~g-eS01O{8C-g#1N6J8nUp%VPo=s^ zCN)N;Z%5d#l3iS`JaY_Us6u zGvSA0WP*}@Vy`_D_4L!U^8z@J)ZGKe3+FVnP;@^Ym;ZS|yZ{b8V?p=lw;C!w3%)`R zFSQSgcsbzyNcHp+T6k6nNu~j2sGXj^MhkPlsJWdlxA^Gw)b*&-#iUWn7s^;5_)05*lMCpXidIqY^2Sxs&9#d=aTTT(GwT&w z8a3j>MeqH^HeZLO1QNQ^-;x4JNX`I*#Tq1!)^H5dXxdTh>w$HvNYu(lpf%A}Vux!; zx@0}j`oN$@9ABI2e{b_OyeyEgeEOEOw|w+Lef_Imqa$ZNBe%qD=<9QS6|1o$+WjVF z5!_UVlPu>P?msyUt|VIbZEVw3fA@T_EMTO z^dQi9uNa*ZSwhMNwB5T&&bE<)G~Kf8xdE&*2xqCn+qbZWQhmD&-3a~XYf!R9C~wo* zA48sTzw7y=KrjyRH7VzCIW51pfhMldx&2N$XSR5h``X!DF<=P3#|iZJ7F%lIZipC& zzYopX=%p6KSE6W@)RzOlsK(wei?jhij%lh27rlUWjxBUxOS)<_!|rIM&4T*!1XeZv zeqWG20|fK(J~osUZC6IjjJof6n>FVANL?D#^UI6cK!~noQ1>7(dIxB@`IaI5_^NJc z1c}^HcZ&8IzX+_eI_35j6hKP~d_7H2S=CkI-$p>%?A;-}%24wfydJmW;xxuXC} z4VDc&OP#6HLQ$M}5#}}|>-9F5T6q(3z^Nl<5A$V_k z{24Z=#9&+OdhMH${1fZOqSb($(@QfSoSlIjWjxw*E2r11FSc-n(;X?F1OF5hx@$j6 zsCh8EG7gmr!YV0LvNeRqbt2DN$kpIX0-aXg(v69TDbKNVl=btf;0z*r~SezTqDO@~|bj9sJLZ=(r85c!$!`Zd)bL0?DXxx{F zH8KjYJFGxb**{k}g~j;}-j*eh`C9P%G_Vtu?b(pBFK;zXj&hW*9?9GdXMSqP0Uw!@ z4n4o^507*53d15O&S6gO^xojJos8Pk z5vE6y^}|k&viV`v6t>f?v~mW$Xq>U;LDBcu5FqHYqbe8gCz_0kdwyyjNrK*8M*Kif z0Z%Kq{Z<&Uw~szY>*x{Q)y-G?l;g?4ZtXR46hDjAUw0s=Tt%e4B7?qA-J@G^VU@G` z?LbON#nyZqLd-QFzj{cZ)N{ue2r6n7ynNPKm)iRh_f&_FSnjGfqMK}p+Uj65RN38< zyU5B3ifu3rxm8!Q!#t}a*DlbM$6XiVuNmK=;l-fyNQmR=Z;-51H`}V}qz1&L{df=X zq1shx0sM!BwYmYgt>v+wj(U~ZYn|YMVFLWn!@hbIYTq>0jflt|&JWy`RR6;TzGL># z3ogq0SliIPVCMZsb5qu)NcE zL-dQ__ocy|FoCexJe}aVTXFLT^5*o)e@<0yzWk$BbOU+7b0bM*gF!qF7Y$0c{U&o) zlp+4O7hO>hvZn2lbnj(F5}TZdGZ}yWMEUz8xE~>>HdflwZUylb$;nC*FiVU@Git_< z3Fh?Ryx~gs{YVMon}FYKU$PGjwK8JXX>qgS0pscVRXsz7y9fG(4L0ysiVt32O3`T( z^m*t2-&#LZ;k=#li^e|>2nlAa;L~!IDHWv~)26opMRBEQ{u7>0x{hKJCnuLi)vvSq zU4#srDJx&f-)A@~4zao)XIm+ej^Q~?>6Ln^ye?Eybb7Zmrrqw zuI#>3J7PvZlO})ci^OOQ&+od!LD$$hX*sPVanDz}KJk8j$(?{j*?IZOK!Vwu7w;}^ zUT?1N@}hl8RoLJOkIHLf(|aCoLwx>?i<_doAwYq;{GOiE9)QaK|sn1c`$T4wUx2;lM z;s*Rasb4Qw*sS9ptc}r}QE1edmz7j;+gAx8D8TN>as!xBVXL-NJXoo!zq}r#B99E^ zKYi!T7G9qlTF^`Y5s{_}_sxAhDCP{Vu=ygn-FjhGi)Vkt88hfJ1P5%*)puzv7i5Uc z%rz6*?!2+8@ZMzKdAqc_4x$%>m@N#QDs^33Br0MH5A4yG&Zh(D^Yx2}_wAwQd~w0ANtr((XxHl&NbL8$HH< zQm20)>#7XnYVcTV8(DnJKef00!!8gqaPsDmqd)K_|BPfGv2xJVq-ANHAc1KJJ16m# zN}Ofl=at?xTc_UD;yL%P`ROj~hKd{}p`3jaP;5Vgy;8)K{U zR$%k*@ybY@@)bEuH;-*mU;kB?zh}QpqL|`ugn!!l#la-{H4D8zX!a+)fX5QjBl6*~ zg*f$!aB^sVuE6m~0p4`@IFi8qp2{b>pHO^-+vFZX)c&lyPXDg`e6rmKeKU$sv=>RW z#L4E-{ZUBhh=?+#h@i478a&c})@K*LXyaqjH7251Qb(ig^IDgP_C84WLaAbEk_xeE6V5$p+x6L+hqu!;K^sdN+{d+-=nEoq_O-M+m)I=FYY7(vq7GSSW!j&WE)0*&N|I85kd wV2Hd7LdQ~;?~NXyJg Date: Tue, 23 Mar 2021 22:41:57 +0100 Subject: [PATCH 3/3] PrivateE2EMedia: Add E2E test for private api /media routes Signed-off-by: Philip Molares --- test/private-api/media.e2e-spec.ts | 138 +++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 test/private-api/media.e2e-spec.ts diff --git a/test/private-api/media.e2e-spec.ts b/test/private-api/media.e2e-spec.ts new file mode 100644 index 000000000..2fc5f7c9a --- /dev/null +++ b/test/private-api/media.e2e-spec.ts @@ -0,0 +1,138 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* eslint-disable +@typescript-eslint/no-unsafe-assignment, +@typescript-eslint/no-unsafe-member-access +*/ + +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import { Test } from '@nestjs/testing'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { promises as fs } from 'fs'; +import * as request from 'supertest'; +import mediaConfigMock from '../../src/config/mock/media.config.mock'; +import appConfigMock from '../../src/config/mock/app.config.mock'; +import authConfigMock from '../../src/config/mock/auth.config.mock'; +import customizationConfigMock from '../../src/config/mock/customization.config.mock'; +import externalConfigMock from '../../src/config/mock/external-services.config.mock'; +import { GroupsModule } from '../../src/groups/groups.module'; +import { LoggerModule } from '../../src/logger/logger.module'; +import { NestConsoleLoggerService } from '../../src/logger/nest-console-logger.service'; +import { MediaModule } from '../../src/media/media.module'; +import { NotesModule } from '../../src/notes/notes.module'; +import { NotesService } from '../../src/notes/notes.service'; +import { PermissionsModule } from '../../src/permissions/permissions.module'; +import { AuthModule } from '../../src/auth/auth.module'; +import { join } from 'path'; +import { PrivateApiModule } from '../../src/api/private/private-api.module'; +import { UsersService } from '../../src/users/users.service'; + +describe('Media', () => { + let app: NestExpressApplication; + let uploadPath: string; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + load: [ + mediaConfigMock, + appConfigMock, + authConfigMock, + customizationConfigMock, + externalConfigMock, + ], + }), + PrivateApiModule, + MediaModule, + TypeOrmModule.forRoot({ + type: 'sqlite', + database: './hedgedoc-e2e-private-media.sqlite', + autoLoadEntities: true, + dropSchema: true, + synchronize: true, + }), + NotesModule, + PermissionsModule, + GroupsModule, + LoggerModule, + AuthModule, + ], + }).compile(); + const config = moduleRef.get(ConfigService); + uploadPath = config.get('mediaConfig').backend.filesystem.uploadPath; + app = moduleRef.createNestApplication(); + app.useStaticAssets(uploadPath, { + prefix: '/uploads', + }); + await app.init(); + const logger = await app.resolve(NestConsoleLoggerService); + logger.log('Switching logger', 'AppBootstrap'); + app.useLogger(logger); + const notesService: NotesService = moduleRef.get('NotesService'); + await notesService.createNote('test content', 'test_upload_media'); + const userService: UsersService = moduleRef.get('UsersService'); + await userService.createUser('hardcoded', 'Testy'); + }); + + describe('POST /media', () => { + it('works', async () => { + const uploadResponse = await request(app.getHttpServer()) + .post('/media') + .attach('file', 'test/private-api/fixtures/test.png') + .set('HedgeDoc-Note', 'test_upload_media') + .expect('Content-Type', /json/) + .expect(201); + const path: string = uploadResponse.body.link; + const testImage = await fs.readFile('test/private-api/fixtures/test.png'); + const downloadResponse = await request(app.getHttpServer()).get(path); + expect(downloadResponse.body).toEqual(testImage); + // Remove /upload/ from path as we just need the filename. + const fileName = path.replace('/uploads/', ''); + // delete the file afterwards + await fs.unlink(join(uploadPath, fileName)); + }); + describe('fails:', () => { + it('MIME type not supported', async () => { + await request(app.getHttpServer()) + .post('/media') + .attach('file', 'test/private-api/fixtures/test.zip') + .set('HedgeDoc-Note', 'test_upload_media') + .expect(400); + expect(await fs.access(uploadPath)).toBeFalsy(); + }); + it('note does not exist', async () => { + await request(app.getHttpServer()) + .post('/media') + .attach('file', 'test/private-api/fixtures/test.zip') + .set('HedgeDoc-Note', 'i_dont_exist') + .expect(400); + expect(await fs.access(uploadPath)).toBeFalsy(); + }); + it('mediaBackend error', async () => { + await fs.rmdir(uploadPath); + await fs.mkdir(uploadPath, { + mode: '444', + }); + await request(app.getHttpServer()) + .post('/media') + .attach('file', 'test/private-api/fixtures/test.png') + .set('HedgeDoc-Note', 'test_upload_media') + .expect('Content-Type', /json/) + .expect(500); + await fs.rmdir(uploadPath); + }); + }); + }); + + afterAll(async () => { + // Delete the upload folder + await fs.rmdir(uploadPath); + }); +});