mirror of
https://github.com/seerr-team/seerr.git
synced 2026-10-12 02:22:17 +00:00
fix(request): restore media status correctly when deleting requests (#3064)
This commit is contained in:
@@ -265,3 +265,273 @@ describe('POST /request/:requestId/retry', () => {
|
||||
assert.ok(persisted.updatedAt > failed.updatedAt);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DELETE /request/:requestId, deleted media status restoration', () => {
|
||||
async function seedDeletedMediaScenario() {
|
||||
const userRepo = getRepository(User);
|
||||
const mediaRepo = getRepository(Media);
|
||||
const requestRepo = getRepository(MediaRequest);
|
||||
|
||||
const admin = await userRepo.findOneOrFail({
|
||||
where: { email: '[email protected]' },
|
||||
});
|
||||
|
||||
const media = await mediaRepo.save(
|
||||
new Media({
|
||||
mediaType: MediaType.MOVIE,
|
||||
tmdbId: 99001,
|
||||
status: MediaStatus.DELETED,
|
||||
status4k: MediaStatus.UNKNOWN,
|
||||
})
|
||||
);
|
||||
|
||||
const staleRequest = await requestRepo.save(
|
||||
new MediaRequest({
|
||||
type: MediaType.MOVIE,
|
||||
status: MediaRequestStatus.COMPLETED,
|
||||
media,
|
||||
requestedBy: admin,
|
||||
is4k: false,
|
||||
isAutoRequest: true,
|
||||
})
|
||||
);
|
||||
|
||||
media.status = MediaStatus.PENDING;
|
||||
await mediaRepo.save(media);
|
||||
|
||||
const newRequest = await requestRepo.save(
|
||||
new MediaRequest({
|
||||
type: MediaType.MOVIE,
|
||||
status: MediaRequestStatus.APPROVED,
|
||||
media,
|
||||
requestedBy: admin,
|
||||
is4k: false,
|
||||
})
|
||||
);
|
||||
|
||||
return { media, staleRequest, newRequest, admin };
|
||||
}
|
||||
|
||||
it('restores media status to DELETED when the re-request is deleted and a stale completed request remains', async () => {
|
||||
const mediaRepo = getRepository(Media);
|
||||
const { media, newRequest } = await seedDeletedMediaScenario();
|
||||
|
||||
const agent = await loginAs('[email protected]', 'test1234');
|
||||
const res = await agent.delete(`/request/${newRequest.id}`);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
|
||||
const updated = await mediaRepo.findOneOrFail({ where: { id: media.id } });
|
||||
assert.strictEqual(updated.status, MediaStatus.DELETED);
|
||||
});
|
||||
|
||||
it('restores media status4k to DELETED when the re-request is deleted and a stale completed request remains', async () => {
|
||||
const userRepo = getRepository(User);
|
||||
const mediaRepo = getRepository(Media);
|
||||
const requestRepo = getRepository(MediaRequest);
|
||||
|
||||
const admin = await userRepo.findOneOrFail({
|
||||
where: { email: '[email protected]' },
|
||||
});
|
||||
|
||||
const media = await mediaRepo.save(
|
||||
new Media({
|
||||
mediaType: MediaType.MOVIE,
|
||||
tmdbId: 99003,
|
||||
status: MediaStatus.UNKNOWN,
|
||||
status4k: MediaStatus.DELETED,
|
||||
})
|
||||
);
|
||||
|
||||
await requestRepo.save(
|
||||
new MediaRequest({
|
||||
type: MediaType.MOVIE,
|
||||
status: MediaRequestStatus.COMPLETED,
|
||||
media,
|
||||
requestedBy: admin,
|
||||
is4k: true,
|
||||
isAutoRequest: true,
|
||||
})
|
||||
);
|
||||
|
||||
media.status4k = MediaStatus.PENDING;
|
||||
await mediaRepo.save(media);
|
||||
|
||||
const newRequest = await requestRepo.save(
|
||||
new MediaRequest({
|
||||
type: MediaType.MOVIE,
|
||||
status: MediaRequestStatus.APPROVED,
|
||||
media,
|
||||
requestedBy: admin,
|
||||
is4k: true,
|
||||
})
|
||||
);
|
||||
|
||||
const agent = await loginAs('[email protected]', 'test1234');
|
||||
const res = await agent.delete(`/request/${newRequest.id}`);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
|
||||
const updated = await mediaRepo.findOneOrFail({ where: { id: media.id } });
|
||||
assert.strictEqual(updated.status4k, MediaStatus.DELETED);
|
||||
});
|
||||
|
||||
it('resets media status to UNKNOWN when the stale completed request is also deleted', async () => {
|
||||
const mediaRepo = getRepository(Media);
|
||||
const requestRepo = getRepository(MediaRequest);
|
||||
const { media, newRequest, staleRequest } =
|
||||
await seedDeletedMediaScenario();
|
||||
|
||||
const agent = await loginAs('[email protected]', 'test1234');
|
||||
|
||||
await agent.delete(`/request/${newRequest.id}`);
|
||||
|
||||
const res = await agent.delete(`/request/${staleRequest.id}`);
|
||||
assert.strictEqual(res.status, 204);
|
||||
|
||||
const updated = await mediaRepo.findOneOrFail({ where: { id: media.id } });
|
||||
assert.strictEqual(updated.status, MediaStatus.UNKNOWN);
|
||||
|
||||
const remaining = await requestRepo.find({
|
||||
where: { media: { id: media.id } },
|
||||
});
|
||||
assert.strictEqual(remaining.length, 0);
|
||||
});
|
||||
|
||||
it('resets media status4k to UNKNOWN when the stale completed 4K request is also deleted', async () => {
|
||||
const userRepo = getRepository(User);
|
||||
const mediaRepo = getRepository(Media);
|
||||
const requestRepo = getRepository(MediaRequest);
|
||||
|
||||
const admin = await userRepo.findOneOrFail({
|
||||
where: { email: '[email protected]' },
|
||||
});
|
||||
|
||||
const media = await mediaRepo.save(
|
||||
new Media({
|
||||
mediaType: MediaType.MOVIE,
|
||||
tmdbId: 99004,
|
||||
status: MediaStatus.UNKNOWN,
|
||||
status4k: MediaStatus.DELETED,
|
||||
})
|
||||
);
|
||||
|
||||
const staleRequest = await requestRepo.save(
|
||||
new MediaRequest({
|
||||
type: MediaType.MOVIE,
|
||||
status: MediaRequestStatus.COMPLETED,
|
||||
media,
|
||||
requestedBy: admin,
|
||||
is4k: true,
|
||||
isAutoRequest: true,
|
||||
})
|
||||
);
|
||||
|
||||
media.status4k = MediaStatus.PENDING;
|
||||
await mediaRepo.save(media);
|
||||
|
||||
const newRequest = await requestRepo.save(
|
||||
new MediaRequest({
|
||||
type: MediaType.MOVIE,
|
||||
status: MediaRequestStatus.APPROVED,
|
||||
media,
|
||||
requestedBy: admin,
|
||||
is4k: true,
|
||||
})
|
||||
);
|
||||
|
||||
const agent = await loginAs('[email protected]', 'test1234');
|
||||
|
||||
await agent.delete(`/request/${newRequest.id}`);
|
||||
|
||||
const res = await agent.delete(`/request/${staleRequest.id}`);
|
||||
assert.strictEqual(res.status, 204);
|
||||
|
||||
const updated = await mediaRepo.findOneOrFail({ where: { id: media.id } });
|
||||
assert.strictEqual(updated.status4k, MediaStatus.UNKNOWN);
|
||||
});
|
||||
|
||||
it('does not reset media status when other active requests still exist', async () => {
|
||||
const userRepo = getRepository(User);
|
||||
const mediaRepo = getRepository(Media);
|
||||
const requestRepo = getRepository(MediaRequest);
|
||||
|
||||
const admin = await userRepo.findOneOrFail({
|
||||
where: { email: '[email protected]' },
|
||||
});
|
||||
|
||||
const media = await mediaRepo.save(
|
||||
new Media({
|
||||
mediaType: MediaType.MOVIE,
|
||||
tmdbId: 99002,
|
||||
status: MediaStatus.PENDING,
|
||||
status4k: MediaStatus.UNKNOWN,
|
||||
})
|
||||
);
|
||||
|
||||
const req1 = await requestRepo.save(
|
||||
new MediaRequest({
|
||||
type: MediaType.MOVIE,
|
||||
status: MediaRequestStatus.PENDING,
|
||||
media,
|
||||
requestedBy: admin,
|
||||
is4k: false,
|
||||
})
|
||||
);
|
||||
|
||||
await requestRepo.save(
|
||||
new MediaRequest({
|
||||
type: MediaType.MOVIE,
|
||||
status: MediaRequestStatus.PENDING,
|
||||
media,
|
||||
requestedBy: admin,
|
||||
is4k: false,
|
||||
})
|
||||
);
|
||||
|
||||
const agent = await loginAs('[email protected]', 'test1234');
|
||||
const res = await agent.delete(`/request/${req1.id}`);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
|
||||
const updated = await mediaRepo.findOneOrFail({ where: { id: media.id } });
|
||||
assert.strictEqual(updated.status, MediaStatus.PENDING);
|
||||
});
|
||||
|
||||
it('does not reset media status when status is PARTIALLY_AVAILABLE and only completed requests remain', async () => {
|
||||
const userRepo = getRepository(User);
|
||||
const mediaRepo = getRepository(Media);
|
||||
const requestRepo = getRepository(MediaRequest);
|
||||
|
||||
const admin = await userRepo.findOneOrFail({
|
||||
where: { email: '[email protected]' },
|
||||
});
|
||||
|
||||
const media = await mediaRepo.save(
|
||||
new Media({
|
||||
mediaType: MediaType.MOVIE,
|
||||
tmdbId: 99005,
|
||||
status: MediaStatus.PARTIALLY_AVAILABLE,
|
||||
status4k: MediaStatus.UNKNOWN,
|
||||
})
|
||||
);
|
||||
|
||||
const completedRequest = await requestRepo.save(
|
||||
new MediaRequest({
|
||||
type: MediaType.MOVIE,
|
||||
status: MediaRequestStatus.COMPLETED,
|
||||
media,
|
||||
requestedBy: admin,
|
||||
is4k: false,
|
||||
})
|
||||
);
|
||||
|
||||
const agent = await loginAs('[email protected]', 'test1234');
|
||||
const res = await agent.delete(`/request/${completedRequest.id}`);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
|
||||
const updated = await mediaRepo.findOneOrFail({ where: { id: media.id } });
|
||||
assert.strictEqual(updated.status, MediaStatus.PARTIALLY_AVAILABLE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -952,13 +952,28 @@ export class MediaRequestSubscriber implements EntitySubscriberInterface<MediaRe
|
||||
relations: { requests: true },
|
||||
});
|
||||
|
||||
const hasActive = fullMedia.requests.some(
|
||||
(request) =>
|
||||
!request.is4k &&
|
||||
request.status !== MediaRequestStatus.COMPLETED &&
|
||||
request.status !== MediaRequestStatus.DECLINED
|
||||
);
|
||||
const hasActive4k = fullMedia.requests.some(
|
||||
(request) =>
|
||||
request.is4k &&
|
||||
request.status !== MediaRequestStatus.COMPLETED &&
|
||||
request.status !== MediaRequestStatus.DECLINED
|
||||
);
|
||||
|
||||
const needsStatusUpdate =
|
||||
!fullMedia.requests.some((request) => !request.is4k) &&
|
||||
fullMedia.status !== MediaStatus.AVAILABLE;
|
||||
!hasActive &&
|
||||
fullMedia.status !== MediaStatus.AVAILABLE &&
|
||||
fullMedia.status !== MediaStatus.PARTIALLY_AVAILABLE;
|
||||
|
||||
const needs4kStatusUpdate =
|
||||
!fullMedia.requests.some((request) => request.is4k) &&
|
||||
fullMedia.status4k !== MediaStatus.AVAILABLE;
|
||||
!hasActive4k &&
|
||||
fullMedia.status4k !== MediaStatus.AVAILABLE &&
|
||||
fullMedia.status4k !== MediaStatus.PARTIALLY_AVAILABLE;
|
||||
|
||||
if (needsStatusUpdate || needs4kStatusUpdate) {
|
||||
// Re-fetch WITHOUT requests to avoid cascade issues on save
|
||||
@@ -967,10 +982,21 @@ export class MediaRequestSubscriber implements EntitySubscriberInterface<MediaRe
|
||||
});
|
||||
|
||||
if (needsStatusUpdate) {
|
||||
cleanMedia.status = MediaStatus.UNKNOWN;
|
||||
const hadCompleted = fullMedia.requests.some(
|
||||
(r) => !r.is4k && r.status === MediaRequestStatus.COMPLETED
|
||||
);
|
||||
cleanMedia.status = hadCompleted
|
||||
? MediaStatus.DELETED
|
||||
: MediaStatus.UNKNOWN;
|
||||
}
|
||||
|
||||
if (needs4kStatusUpdate) {
|
||||
cleanMedia.status4k = MediaStatus.UNKNOWN;
|
||||
const hadCompleted4k = fullMedia.requests.some(
|
||||
(r) => r.is4k && r.status === MediaRequestStatus.COMPLETED
|
||||
);
|
||||
cleanMedia.status4k = hadCompleted4k
|
||||
? MediaStatus.DELETED
|
||||
: MediaStatus.UNKNOWN;
|
||||
}
|
||||
|
||||
await manager.save(cleanMedia);
|
||||
|
||||
Reference in New Issue
Block a user