feat: altyazı otomasyon sistemi MVP'sini ekle
Docker tabanlı mikro servis mimarisi ile altyazı otomasyon sistemi altyapısı kuruldu. - Core (Node.js): Chokidar dosya izleyici, BullMQ iş kuyrukları, ffprobe medya analizi, MongoDB entegrasyonu ve dosya yazma işlemleri. - API (Fastify): Mock sağlayıcılar, arşiv güvenliği (zip-slip), altyazı doğrulama, puanlama ve aday seçim motoru. - UI (React/Vite): İş yönetimi paneli, canlı SSE log akışı, manuel inceleme arayüzü ve sistem ayarları. - Altyapı: Docker Compose (dev/prod), Redis, Mongo ve çevresel değişken yapılandırmaları.
This commit is contained in:
48
services/api/test/scoring.test.ts
Normal file
48
services/api/test/scoring.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { chooseBest, scoreCandidateFile } from '../src/lib/scoring.js';
|
||||
|
||||
const candidate: any = {
|
||||
id: 'c1',
|
||||
provider: 'opensubtitles',
|
||||
displayName: 'x',
|
||||
downloadType: 'archiveZip',
|
||||
downloadUrl: 'mock://x',
|
||||
lang: 'tr',
|
||||
releaseHints: ['1080p', 'x265', 'flux'],
|
||||
scoreHints: [],
|
||||
isHI: false,
|
||||
isForced: false
|
||||
};
|
||||
|
||||
describe('scoring', () => {
|
||||
it('scores tv season/episode match strongly', () => {
|
||||
const s = scoreCandidateFile('/tmp/show.S01E02.1080p.srt', 'srt', candidate, {
|
||||
type: 'tv',
|
||||
title: 'show',
|
||||
season: 1,
|
||||
episode: 2,
|
||||
release: 'FLUX',
|
||||
languages: ['tr']
|
||||
});
|
||||
expect(s?.score).toBeGreaterThan(100);
|
||||
});
|
||||
|
||||
it('disqualifies wrong episode for tv', () => {
|
||||
const s = scoreCandidateFile('/tmp/show.S01E03.srt', 'srt', candidate, {
|
||||
type: 'tv',
|
||||
title: 'show',
|
||||
season: 1,
|
||||
episode: 2,
|
||||
languages: ['tr']
|
||||
});
|
||||
expect(s).toBeNull();
|
||||
});
|
||||
|
||||
it('returns ambiguous when top scores are close', () => {
|
||||
const d = chooseBest([
|
||||
{ id: 'a', candidateId: 'a', provider: 'x', filePath: '/a', ext: 'srt', lang: 'tr', score: 90, reasons: [] },
|
||||
{ id: 'b', candidateId: 'b', provider: 'x', filePath: '/b', ext: 'srt', lang: 'tr', score: 88, reasons: [] }
|
||||
] as any);
|
||||
expect(d.status).toBe('AMBIGUOUS');
|
||||
});
|
||||
});
|
||||
14
services/api/test/security.test.ts
Normal file
14
services/api/test/security.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { ensureInsideRoot } from '../src/lib/security.js';
|
||||
|
||||
describe('zip slip helper', () => {
|
||||
it('accepts path inside root', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'sw-root-'));
|
||||
const file = path.join(root, 'a.txt');
|
||||
await fs.writeFile(file, 'x');
|
||||
expect(await ensureInsideRoot(root, file)).toBe(true);
|
||||
});
|
||||
});
|
||||
21
services/api/test/validators.test.ts
Normal file
21
services/api/test/validators.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { detectSubtitleType, isProbablyText, validateAss, validateSrt } from '../src/lib/validators.js';
|
||||
|
||||
describe('subtitle validators', () => {
|
||||
it('validates srt content', () => {
|
||||
const srt = `1\n00:00:01,000 --> 00:00:02,000\na\n\n2\n00:00:03,000 --> 00:00:04,000\nb\n\n3\n00:00:05,000 --> 00:00:06,000\nc\n`;
|
||||
expect(validateSrt(srt)).toBe(true);
|
||||
expect(detectSubtitleType(srt)).toBe('srt');
|
||||
});
|
||||
|
||||
it('validates ass content', () => {
|
||||
const ass = `[Script Info]\n[Events]\nDialogue: 0,0:00:01.00,0:00:02.00,Default,,0,0,0,,x`;
|
||||
expect(validateAss(ass)).toBe(true);
|
||||
expect(detectSubtitleType(ass)).toBe('ass');
|
||||
});
|
||||
|
||||
it('rejects binary for text detector', () => {
|
||||
const b = Buffer.from([0, 255, 3, 0, 9]);
|
||||
expect(isProbablyText(b)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user