Test modülü eklendi

This commit is contained in:
2025-11-26 23:14:41 +03:00
parent c19351f434
commit f6b73dacd2
12 changed files with 490 additions and 29 deletions

View File

@@ -0,0 +1,181 @@
import fs from "fs";
import path from "path";
import { spawn } from "child_process";
import { Server } from "socket.io";
import { Job, JobDocument, TimeUnit } from "../models/job.js";
const repoBaseDir = path.join(process.cwd(), "test-runs");
function unitToMs(unit: TimeUnit) {
if (unit === "dakika") return 60_000;
if (unit === "saat") return 60 * 60_000;
return 24 * 60 * 60_000;
}
function ensureDir(dir: string) {
return fs.promises.mkdir(dir, { recursive: true });
}
function cleanOutput(input: string) {
// ANSI escape sequences temizleme
return input.replace(
// eslint-disable-next-line no-control-regex
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
""
);
}
function runCommand(command: string, cwd: string, onData: (chunk: string) => void) {
return new Promise<void>((resolve, reject) => {
const child = spawn(command, {
cwd,
shell: true,
env: { ...process.env, CI: process.env.CI || "1" }
});
child.stdout.on("data", (data) => onData(cleanOutput(data.toString())));
child.stderr.on("data", (data) => onData(cleanOutput(data.toString())));
child.on("error", (err) => {
onData(`Hata: ${err.message}`);
reject(err);
});
child.on("close", (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Komut kod ${code} ile kapandı`));
}
});
});
}
async function cloneOrPull(job: JobDocument, onData: (chunk: string) => void) {
const repoDir = path.join(repoBaseDir, job._id.toString());
await ensureDir(repoDir);
const gitDir = path.join(repoDir, ".git");
const exists = fs.existsSync(gitDir);
if (!exists) {
onData(`Repo klonlanıyor: ${job.repoUrl}`);
await runCommand(`git clone ${job.repoUrl} ${repoDir}`, process.cwd(), onData);
} else {
onData("Repo güncelleniyor (git pull)...");
await runCommand("git pull", repoDir, onData);
}
return repoDir;
}
async function ensureDependencies(repoDir: string, onData: (chunk: string) => void) {
const nodeModules = path.join(repoDir, "node_modules");
const hasPackageJson = fs.existsSync(path.join(repoDir, "package.json"));
if (!hasPackageJson) {
onData("package.json bulunamadı, npm install atlanıyor");
return;
}
if (fs.existsSync(nodeModules)) {
onData("Bağımlılıklar mevcut, npm install atlanıyor");
return;
}
onData("npm install çalıştırılıyor...");
await runCommand("npm install", repoDir, (line) => onData(line));
}
class JobService {
private timers: Map<string, NodeJS.Timeout> = new Map();
private io: Server | null = null;
setSocket(io: Server) {
this.io = io;
}
private emitStatus(jobId: string, payload: Partial<JobDocument>) {
if (!this.io) return;
const body = {
jobId,
status: payload.status,
lastRunAt: payload.lastRunAt,
lastDurationMs: payload.lastDurationMs,
lastMessage: payload.lastMessage
};
this.io.to(`job:${jobId}`).emit("job:status", body);
this.io.emit("job:status", body);
}
private emitLog(jobId: string, line: string) {
if (!this.io) return;
this.io.to(`job:${jobId}`).emit("job:log", { jobId, line });
}
async runJob(jobId: string) {
const job = await Job.findById(jobId);
if (!job) return;
const startedAt = Date.now();
await Job.findByIdAndUpdate(jobId, { status: "running", lastMessage: "Çalıştırılıyor..." });
this.emitStatus(jobId, { status: "running", lastMessage: "Çalıştırılıyor..." } as JobDocument);
try {
const repoDir = await cloneOrPull(job, (line) => this.emitLog(jobId, line));
await ensureDependencies(repoDir, (line) => this.emitLog(jobId, line));
this.emitLog(jobId, `Test komutu çalıştırılıyor: ${job.testCommand}`);
await runCommand(job.testCommand, repoDir, (line) => this.emitLog(jobId, line));
this.emitLog(jobId, "Test tamamlandı: Başarılı");
const duration = Date.now() - startedAt;
await Job.findByIdAndUpdate(jobId, {
status: "success",
lastRunAt: new Date(),
lastDurationMs: duration,
lastMessage: "Başarılı"
});
this.emitStatus(jobId, {
status: "success",
lastRunAt: new Date(),
lastDurationMs: duration,
lastMessage: "Başarılı"
} as JobDocument);
} catch (err) {
const duration = Date.now() - startedAt;
await Job.findByIdAndUpdate(jobId, {
status: "failed",
lastRunAt: new Date(),
lastDurationMs: duration,
lastMessage: (err as Error).message
});
this.emitLog(jobId, `Hata: ${(err as Error).message}`);
this.emitStatus(jobId, {
status: "failed",
lastRunAt: new Date(),
lastDurationMs: duration,
lastMessage: (err as Error).message
} as JobDocument);
this.emitLog(jobId, "Test tamamlandı: Hata");
}
}
scheduleJob(job: JobDocument) {
const intervalMs = job.checkValue * unitToMs(job.checkUnit);
if (!intervalMs || Number.isNaN(intervalMs)) return;
this.clearJob(job._id.toString());
const timer = setInterval(() => this.runJob(job._id.toString()), intervalMs);
this.timers.set(job._id.toString(), timer);
}
clearJob(jobId: string) {
const timer = this.timers.get(jobId);
if (timer) {
clearInterval(timer);
this.timers.delete(jobId);
}
}
async bootstrap() {
const jobs = await Job.find();
jobs.forEach((job) => this.scheduleJob(job));
}
}
export const jobService = new JobService();