mirror of
https://github.com/seerr-team/seerr.git
synced 2026-04-17 22:07:59 -04:00
268 lines
7.8 KiB
TypeScript
268 lines
7.8 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { before, beforeEach, describe, it, mock } from 'node:test';
|
|
|
|
import {
|
|
MediaRequestStatus,
|
|
MediaStatus,
|
|
MediaType,
|
|
} from '@server/constants/media';
|
|
import { getRepository } from '@server/datasource';
|
|
import Media from '@server/entity/Media';
|
|
import { MediaRequest } from '@server/entity/MediaRequest';
|
|
import { User } from '@server/entity/User';
|
|
import { getSettings } from '@server/lib/settings';
|
|
import { checkUser } from '@server/middleware/auth';
|
|
import { setupTestDb } from '@server/test/db';
|
|
import type { Express } from 'express';
|
|
import express from 'express';
|
|
import session from 'express-session';
|
|
import request from 'supertest';
|
|
import authRoutes from './auth';
|
|
import requestRoutes from './request';
|
|
|
|
const sendNotificationMock = mock.method(
|
|
MediaRequest,
|
|
'sendNotification',
|
|
async () => undefined
|
|
).mock;
|
|
|
|
let app: Express;
|
|
|
|
function createApp() {
|
|
const app = express();
|
|
app.use(express.json());
|
|
app.use(
|
|
session({
|
|
secret: 'test-secret',
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
})
|
|
);
|
|
app.use(checkUser);
|
|
app.use('/auth', authRoutes);
|
|
app.use('/request', requestRoutes);
|
|
app.use(
|
|
(
|
|
err: { status?: number; message?: string },
|
|
_req: express.Request,
|
|
res: express.Response,
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
_next: express.NextFunction
|
|
) => {
|
|
res
|
|
.status(err.status ?? 500)
|
|
.json({ status: err.status ?? 500, message: err.message });
|
|
}
|
|
);
|
|
return app;
|
|
}
|
|
|
|
before(async () => {
|
|
app = createApp();
|
|
});
|
|
|
|
beforeEach(() => {
|
|
sendNotificationMock.resetCalls();
|
|
});
|
|
|
|
setupTestDb();
|
|
|
|
async function loginAs(email: string, password: string) {
|
|
const settings = getSettings();
|
|
const priorLocalLogin = settings.main.localLogin;
|
|
settings.main.localLogin = true;
|
|
|
|
try {
|
|
const agent = request.agent(app);
|
|
const res = await agent.post('/auth/local').send({ email, password });
|
|
assert.strictEqual(res.status, 200);
|
|
return agent;
|
|
} finally {
|
|
settings.main.localLogin = priorLocalLogin;
|
|
}
|
|
}
|
|
|
|
async function seedRequest(status = MediaRequestStatus.PENDING) {
|
|
const userRepo = getRepository(User);
|
|
const mediaRepo = getRepository(Media);
|
|
const requestRepo = getRepository(MediaRequest);
|
|
|
|
const requestedBy = await userRepo.findOneOrFail({
|
|
where: { email: 'friend@seerr.dev' },
|
|
});
|
|
|
|
const media = await mediaRepo.save(
|
|
new Media({
|
|
mediaType: MediaType.MOVIE,
|
|
tmdbId: 12345,
|
|
status: MediaStatus.UNKNOWN,
|
|
status4k: MediaStatus.UNKNOWN,
|
|
})
|
|
);
|
|
|
|
const created = await requestRepo.save(
|
|
new MediaRequest({
|
|
type: MediaType.MOVIE,
|
|
status,
|
|
media,
|
|
requestedBy,
|
|
is4k: false,
|
|
updatedAt: new Date('2025-03-01T00:00:00.000Z'),
|
|
})
|
|
);
|
|
|
|
return requestRepo.findOneOrFail({
|
|
where: { id: created.id },
|
|
relations: { requestedBy: true, modifiedBy: true },
|
|
});
|
|
}
|
|
|
|
describe('DELETE /request/:requestId', () => {
|
|
it('allows the owner to delete their own pending request', async () => {
|
|
const mediaRequest = await seedRequest();
|
|
|
|
const agent = await loginAs('friend@seerr.dev', 'test1234');
|
|
const res = await agent.delete(`/request/${mediaRequest.id}`);
|
|
|
|
assert.strictEqual(res.status, 204);
|
|
});
|
|
|
|
it('allows an admin to delete any pending request', async () => {
|
|
const mediaRequest = await seedRequest();
|
|
|
|
const agent = await loginAs('admin@seerr.dev', 'test1234');
|
|
const res = await agent.delete(`/request/${mediaRequest.id}`);
|
|
|
|
assert.strictEqual(res.status, 204);
|
|
});
|
|
|
|
it('prevents a non-owner non-admin from deleting a pending request', async () => {
|
|
const userRepo = getRepository(User);
|
|
const mediaRepo = getRepository(Media);
|
|
const requestRepo = getRepository(MediaRequest);
|
|
|
|
// Create a request owned by admin, then try to delete as friend
|
|
const owner = await userRepo.findOneOrFail({
|
|
where: { email: 'admin@seerr.dev' },
|
|
});
|
|
|
|
const media = await mediaRepo.save(
|
|
new Media({
|
|
mediaType: MediaType.MOVIE,
|
|
tmdbId: 54321,
|
|
status: MediaStatus.UNKNOWN,
|
|
status4k: MediaStatus.UNKNOWN,
|
|
})
|
|
);
|
|
|
|
const mediaRequest = await requestRepo.save(
|
|
new MediaRequest({
|
|
type: MediaType.MOVIE,
|
|
status: MediaRequestStatus.PENDING,
|
|
media,
|
|
requestedBy: owner,
|
|
is4k: false,
|
|
})
|
|
);
|
|
|
|
const agent = await loginAs('friend@seerr.dev', 'test1234');
|
|
const res = await agent.delete(`/request/${mediaRequest.id}`);
|
|
|
|
assert.strictEqual(res.status, 401);
|
|
});
|
|
|
|
it('prevents the owner from deleting an approved request', async () => {
|
|
const mediaRequest = await seedRequest(MediaRequestStatus.APPROVED);
|
|
|
|
const agent = await loginAs('friend@seerr.dev', 'test1234');
|
|
const res = await agent.delete(`/request/${mediaRequest.id}`);
|
|
|
|
assert.strictEqual(res.status, 401);
|
|
});
|
|
|
|
it('returns 404 for a non-existent request', async () => {
|
|
const agent = await loginAs('admin@seerr.dev', 'test1234');
|
|
const res = await agent.delete('/request/99999999');
|
|
|
|
assert.strictEqual(res.status, 404);
|
|
});
|
|
});
|
|
|
|
describe('PUT /request/:requestId (movie)', () => {
|
|
it('persists server and root folder changes to the database', async () => {
|
|
const requestRepo = getRepository(MediaRequest);
|
|
const mediaRequest = await seedRequest();
|
|
|
|
const agent = await loginAs('admin@seerr.dev', 'test1234');
|
|
const res = await agent.put(`/request/${mediaRequest.id}`).send({
|
|
mediaType: MediaType.MOVIE,
|
|
serverId: 3,
|
|
profileId: 7,
|
|
rootFolder: '/updated/movies',
|
|
tags: [1, 2],
|
|
});
|
|
|
|
assert.strictEqual(res.status, 200);
|
|
|
|
const saved = await requestRepo.findOneOrFail({
|
|
where: { id: mediaRequest.id },
|
|
});
|
|
assert.strictEqual(saved.serverId, 3);
|
|
assert.strictEqual(saved.profileId, 7);
|
|
assert.strictEqual(saved.rootFolder, '/updated/movies');
|
|
});
|
|
});
|
|
|
|
describe('POST /request/:requestId/:status', () => {
|
|
const cases = [
|
|
{ action: 'approve', expected: MediaRequestStatus.APPROVED },
|
|
{ action: 'decline', expected: MediaRequestStatus.DECLINED },
|
|
] as const;
|
|
|
|
for (const { action, expected } of cases) {
|
|
it(`transitions to ${action}d and records the acting user`, async () => {
|
|
const repo = getRepository(MediaRequest);
|
|
const pending = await seedRequest();
|
|
const admin = await loginAs('admin@seerr.dev', 'test1234');
|
|
|
|
const res = await admin.post(`/request/${pending.id}/${action}`);
|
|
|
|
assert.strictEqual(res.status, 200);
|
|
assert.strictEqual(res.body.status, expected);
|
|
assert.strictEqual(res.body.modifiedBy.email, 'admin@seerr.dev');
|
|
|
|
const persisted = await repo.findOneOrFail({
|
|
where: { id: pending.id },
|
|
relations: { modifiedBy: true },
|
|
});
|
|
|
|
assert.strictEqual(persisted.status, expected);
|
|
assert.strictEqual(persisted.modifiedBy?.email, 'admin@seerr.dev');
|
|
assert.ok(persisted.updatedAt > pending.updatedAt);
|
|
});
|
|
}
|
|
});
|
|
|
|
describe('POST /request/:requestId/retry', () => {
|
|
it('re-approves a failed request and records the acting user', async () => {
|
|
const repo = getRepository(MediaRequest);
|
|
const failed = await seedRequest(MediaRequestStatus.FAILED);
|
|
const admin = await loginAs('admin@seerr.dev', 'test1234');
|
|
|
|
const res = await admin.post(`/request/${failed.id}/retry`);
|
|
|
|
assert.strictEqual(res.status, 200);
|
|
assert.strictEqual(res.body.status, MediaRequestStatus.APPROVED);
|
|
assert.strictEqual(res.body.modifiedBy.email, 'admin@seerr.dev');
|
|
|
|
const persisted = await repo.findOneOrFail({
|
|
where: { id: failed.id },
|
|
relations: { modifiedBy: true },
|
|
});
|
|
|
|
assert.strictEqual(persisted.status, MediaRequestStatus.APPROVED);
|
|
assert.strictEqual(persisted.modifiedBy?.email, 'admin@seerr.dev');
|
|
assert.ok(persisted.updatedAt > failed.updatedAt);
|
|
});
|
|
});
|