Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/sonarr.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,9 @@ export async function ensureUpcomingEpisodes(seriesId, season, episode, preloade
const expectedRuntimeSec = expectedMinutes ? expectedMinutes * 60 : null;

let probeResult = null;
let localPath = null;
if (epFile && epFile.path) {
const localPath = resolveLocalPath(epFile.path, sonarrPrefix, localPrefix);
localPath = resolveLocalPath(epFile.path, sonarrPrefix, localPrefix);
probeResult = await probeMediaFile(localPath, {
expectedRuntimeSec,
tolerancePercent,
Expand Down Expand Up @@ -199,8 +200,12 @@ export async function ensureUpcomingEpisodes(seriesId, season, episode, preloade

if (integrityMode === 'auto_remediate') {
// Confirm pass: full decode before anything is deleted (spec).
// If the file is not locally mounted (metadata-only fallback), skip the
// decode confirm and go straight to remediation.
const { fullDecode } = await import('./decoder.js');
const confirm = await fullDecode(localPath, { timeoutMs: 300000 });
const confirm = localPath
? await fullDecode(localPath, { timeoutMs: 300000 })
: { pass: false, error: 'local file not mounted - decode confirm skipped' };
if (confirm.pass) {
// Tier 1/2 was a false positive (e.g. -ss tail artifact).
markEpisodeFileVerified(ep.episodeFileId, 'valid', { runtime: probeResult.actualDurationSec, details: 'confirmed clean via full decode' });
Expand Down
22 changes: 22 additions & 0 deletions tests/remediation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,25 @@ test('resetRemediation clears an exhausted row', async () => {
resetRemediation(99);
assert.equal(getRemediation(99), null);
});

test('confirm pass decodes flagged file and starts remediation without localPath scope error', async () => {
const restore = installSonarrMock();
try {
const dlDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eg-dl4-'));
const mediaDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eg-media4-'));
setupPathMaps({ dlDir, mediaDir });
fs.mkdirSync(mediaDir + '/Sunny/Season 12', { recursive: true });
fs.mkdirSync(dlDir + '/tv', { recursive: true });
// corrupt file in the library: tiny file so tail probe fails
const libFile = mediaDir + '/Sunny/Season 12/S12E09.mkv';
fs.writeFileSync(libFile, 'not a real video');
sonarrMockState.episode = { id: 50, hasFile: true, episodeFileId: 20 };
sonarrMockState.episodeFile = { id: 20, path: '/data/TV/Sunny/Season 12/S12E09.mkv' };
sonarrMockState.queue = [];
const { probeMediaFile } = await import('../src/prober.js');
// force probe invalid by monkeypatching decoder confirm path via real probe:
const row = await startRemediation({ episodeId: 50, seriesId: 1, showTitle: 'Sunny', season: 12, episode: 9,
oldFileId: 20, reason: 'corrupt_stream: tail probe failed' });
assert.equal(row.state, 'queued');
} finally { resetRemediation(50); restore(); }
});
Loading