mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2026-08-07 07:14:49 +00:00
c2ccf48c39
Previously, media uploads were associated with one note upload but served regardless of the requesting user's permission. As we have a fine-grade permission system in place, we can use this as well for enforcing permission checks on media uploads. In order to make an upload reusable across multiple notes, this adds the capability to link one media upload to multiple notes and unlink that accordingly again. Signed-off-by: Erik Michelson <github@erik.michelson.eu>
251 lines
8.6 KiB
TypeScript
251 lines
8.6 KiB
TypeScript
/*
|
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
|
|
import { PUBLIC_API_PREFIX } from '../../src/app.module';
|
|
import type { MediaUploadDto } from '../../src/dtos/media-upload.dto';
|
|
import { ConsoleLoggerService } from '../../src/logger/console-logger.service';
|
|
import { getCurrentDateTime, isoStringToDateTime } from '../../src/utils/datetime';
|
|
import type { TestSetup } from '../test-setup';
|
|
import { noteAlias1, TestSetupBuilder, username1 } from '../test-setup';
|
|
import { ensureDeleted } from '../utils';
|
|
import { promises as fs } from 'fs';
|
|
import { join } from 'path';
|
|
import request from 'supertest';
|
|
|
|
describe('Media', () => {
|
|
let testSetup: TestSetup;
|
|
let agent: request.SuperAgentTest;
|
|
|
|
let testImage: Buffer;
|
|
let uploadPath: string;
|
|
|
|
beforeEach(async () => {
|
|
testSetup = await TestSetupBuilder.create().withUsers().build();
|
|
|
|
testImage = await fs.readFile('test/public-api/fixtures/test.png');
|
|
uploadPath = testSetup.configService.get('mediaConfig').backend.filesystem.uploadPath;
|
|
|
|
await testSetup.init();
|
|
|
|
agent = request.agent(testSetup.app.getHttpServer());
|
|
|
|
const logger = await testSetup.app.resolve(ConsoleLoggerService);
|
|
logger.log('Switching logger', 'AppBootstrap');
|
|
testSetup.app.useLogger(logger);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
// Delete the upload folder
|
|
await ensureDeleted(uploadPath);
|
|
await testSetup.cleanup();
|
|
});
|
|
|
|
describe(`POST ${PUBLIC_API_PREFIX}/media`, () => {
|
|
it('uploads image', async () => {
|
|
const uploadResponse = await agent
|
|
.post(`${PUBLIC_API_PREFIX}/media`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[0].secret}`)
|
|
.attach('file', 'test/public-api/fixtures/test.png')
|
|
.set('HedgeDoc-Note', noteAlias1)
|
|
.expect('Content-Type', /json/)
|
|
.expect(201);
|
|
const uuid = uploadResponse.body.uuid;
|
|
const file = await fs.readFile(join(uploadPath, uuid + '.png'));
|
|
expect(file).toEqual(testImage);
|
|
});
|
|
it('uploads image and uppercase note alias', async () => {
|
|
const uploadResponse = await agent
|
|
.post(`${PUBLIC_API_PREFIX}/media`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[0].secret}`)
|
|
.attach('file', 'test/public-api/fixtures/test.png')
|
|
.set('HedgeDoc-Note', noteAlias1.toUpperCase())
|
|
.expect('Content-Type', /json/)
|
|
.expect(201);
|
|
const uuid = uploadResponse.body.uuid;
|
|
const file = await fs.readFile(join(uploadPath, uuid + '.png'));
|
|
expect(file).toEqual(testImage);
|
|
});
|
|
describe('fails:', () => {
|
|
beforeEach(async () => {
|
|
await ensureDeleted(uploadPath);
|
|
});
|
|
it('MIME type not supported', async () => {
|
|
await agent
|
|
.post(`${PUBLIC_API_PREFIX}/media`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[0].secret}`)
|
|
.attach('file', 'test/public-api/fixtures/test.zip')
|
|
.set('HedgeDoc-Note', noteAlias1)
|
|
.expect(400);
|
|
await expect(fs.access(uploadPath)).rejects.toBeDefined();
|
|
});
|
|
it('note does not exist', async () => {
|
|
await agent
|
|
.post(`${PUBLIC_API_PREFIX}/media`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[0].secret}`)
|
|
.attach('file', 'test/public-api/fixtures/test.zip')
|
|
.set('HedgeDoc-Note', 'i_dont_exist')
|
|
.expect(404);
|
|
await expect(fs.access(uploadPath)).rejects.toBeDefined();
|
|
});
|
|
it('mediaBackend error', async () => {
|
|
await fs.mkdir(uploadPath, {
|
|
mode: '444',
|
|
});
|
|
await agent
|
|
.post(`${PUBLIC_API_PREFIX}/media`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[0].secret}`)
|
|
.attach('file', 'test/public-api/fixtures/test.png')
|
|
.set('HedgeDoc-Note', noteAlias1)
|
|
.expect('Content-Type', /json/)
|
|
.expect(500);
|
|
});
|
|
it('no file uploaded', async () => {
|
|
await agent
|
|
.post(`${PUBLIC_API_PREFIX}/media`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[0].secret}`)
|
|
.set('HedgeDoc-Note', noteAlias1)
|
|
.expect('Content-Type', /json/)
|
|
.expect(400);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await ensureDeleted(uploadPath);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe(`GET ${PUBLIC_API_PREFIX}/media/{:uuid}`, () => {
|
|
const fileName = 'test.png';
|
|
it('returns the media info', async () => {
|
|
jest.useFakeTimers({
|
|
legacyFakeTimers: true,
|
|
});
|
|
const hardCodedNow = getCurrentDateTime();
|
|
const uuid = await testSetup.mediaService.saveFile(
|
|
fileName,
|
|
testImage,
|
|
testSetup.userIds[0],
|
|
testSetup.ownedNoteIds[0],
|
|
);
|
|
const response = await agent
|
|
.get(`${PUBLIC_API_PREFIX}/media/${uuid}`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[0].secret}`)
|
|
.expect('Content-Type', /json/)
|
|
.expect(200);
|
|
const mediaDto: MediaUploadDto = response.body;
|
|
expect(mediaDto.uuid).toEqual(uuid);
|
|
expect(
|
|
isoStringToDateTime(mediaDto.createdAt).toMillis() - hardCodedNow.toMillis(),
|
|
).toBeLessThan(100);
|
|
expect(mediaDto.linkedNoteCount).toEqual(1);
|
|
expect(mediaDto.fileName).toEqual(fileName);
|
|
expect(mediaDto.username).toEqual(username1);
|
|
jest.useRealTimers();
|
|
});
|
|
});
|
|
|
|
describe(`DELETE ${PUBLIC_API_PREFIX}/media/{:uuid}`, () => {
|
|
it('successfully deletes an uploaded file', async () => {
|
|
const upload = await testSetup.mediaService.saveFile(
|
|
'test.png',
|
|
testImage,
|
|
testSetup.userIds[0],
|
|
testSetup.ownedNoteIds[0],
|
|
);
|
|
await agent
|
|
.delete(`${PUBLIC_API_PREFIX}/media/${upload}`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[0].secret}`)
|
|
.expect(204);
|
|
});
|
|
it('deleting user is owner of file', async () => {
|
|
// upload a file with the default test user
|
|
const testNote = await testSetup.notesService.createNote(
|
|
'test content',
|
|
testSetup.userIds[0],
|
|
'test_delete_media_file',
|
|
);
|
|
const upload = await testSetup.mediaService.saveFile(
|
|
'test.png',
|
|
testImage,
|
|
testSetup.userIds[0],
|
|
testNote,
|
|
);
|
|
|
|
// try to delete upload with second user
|
|
await agent
|
|
.delete(`${PUBLIC_API_PREFIX}/media/${upload}`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[1].secret}`)
|
|
.expect(403);
|
|
|
|
await agent
|
|
.get(`/uploads/${upload}.png`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[1].secret}`)
|
|
.expect(200);
|
|
|
|
// delete upload for real
|
|
await agent
|
|
.delete(`${PUBLIC_API_PREFIX}/media/${upload}`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[0].secret}`)
|
|
.expect(204);
|
|
|
|
// Test if file is really deleted
|
|
await agent
|
|
.get(`/uploads/${upload}.png`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[0].secret}`)
|
|
.expect(404);
|
|
});
|
|
it('deleting user is owner of note', async () => {
|
|
// upload a file with the default test user
|
|
const testNote = await testSetup.notesService.createNote(
|
|
'test content',
|
|
testSetup.userIds[2],
|
|
'test_delete_media_note',
|
|
);
|
|
const upload = await testSetup.mediaService.saveFile(
|
|
'test.png',
|
|
testImage,
|
|
testSetup.userIds[0],
|
|
testNote,
|
|
);
|
|
|
|
// try to delete upload with second user
|
|
await agent
|
|
.delete(`${PUBLIC_API_PREFIX}/media/${upload}`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[1].secret}`)
|
|
.expect(403);
|
|
|
|
await agent
|
|
.get(`/uploads/${upload}.png`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[1].secret}`)
|
|
.expect(200);
|
|
|
|
// delete upload for real
|
|
await agent
|
|
.delete(`${PUBLIC_API_PREFIX}/media/${upload}`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[2].secret}`)
|
|
.expect(204);
|
|
|
|
// Test if file is really deleted
|
|
await agent
|
|
.get(`/uploads/${upload}.png`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[2].secret}`)
|
|
.expect(404);
|
|
});
|
|
it('errors if the user does not own the file', async () => {
|
|
const uuid = await testSetup.mediaService.saveFile(
|
|
'test.png',
|
|
testImage,
|
|
testSetup.userIds[0],
|
|
testSetup.ownedNoteIds[0],
|
|
);
|
|
await agent
|
|
.delete(`${PUBLIC_API_PREFIX}/media/${uuid}`)
|
|
.set('Authorization', `Bearer ${testSetup.authTokens[1].secret}`)
|
|
.expect(403);
|
|
});
|
|
});
|
|
});
|