Compare commits
No commits in common. "ec6fc53c81095109251335a0aa10d2460e7911ca" and "a058cc89207fa558290267579517b5d97feae004" have entirely different histories.
ec6fc53c81
...
a058cc8920
64 changed files with 163 additions and 1168 deletions
|
|
@ -462,8 +462,12 @@
|
||||||
|
|
||||||
const footerLoginBtn = document.getElementById('footer-cta-login');
|
const footerLoginBtn = document.getElementById('footer-cta-login');
|
||||||
if (footerLoginBtn) footerLoginBtn.style.display = 'inline-flex';
|
if (footerLoginBtn) footerLoginBtn.style.display = 'inline-flex';
|
||||||
|
|
||||||
if (landingBanner) landingBanner.style.display = 'none';
|
if (landingBanner) landingBanner.style.display = 'none';
|
||||||
if (navDashLink) navDashLink.style.display = 'none';
|
if (navDashLink) navDashLink.style.display = 'none';
|
||||||
|
if (mobDashLink) mobDashLink.style.display = 'none';
|
||||||
|
const mobLogoutBtn = document.getElementById('mobile-drawer-logout-btn');
|
||||||
|
if (mobLogoutBtn) mobLogoutBtn.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔒 Strikter Rollen-Filter für Dozenten- und Admin-Funktionen
|
// 🔒 Strikter Rollen-Filter für Dozenten- und Admin-Funktionen
|
||||||
|
|
@ -709,7 +713,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialisierung beim Laden des DOMs
|
// Initialisierung beim Laden des DOMs
|
||||||
function initAuth() {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
window.AUTH.updateUI();
|
window.AUTH.updateUI();
|
||||||
window.AUTH.checkStatus();
|
window.AUTH.checkStatus();
|
||||||
window.AUTH.fetchMe();
|
window.AUTH.fetchMe();
|
||||||
|
|
@ -723,11 +727,5 @@
|
||||||
window.location.replace('dashboard.html');
|
window.location.replace('dashboard.html');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
|
||||||
document.addEventListener('DOMContentLoaded', initAuth);
|
|
||||||
} else {
|
|
||||||
initAuth();
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -162,25 +162,15 @@
|
||||||
|
|
||||||
logToTerminal(`🧪 Führe automatisierte Tests aus (Strg+Shift+Enter)...\n----------------------------------------\n`, "info");
|
logToTerminal(`🧪 Führe automatisierte Tests aus (Strg+Shift+Enter)...\n----------------------------------------\n`, "info");
|
||||||
|
|
||||||
const result = await runner.runTests(userCode, currentChapterData.testCode, logToTerminal, currentChapterData);
|
const result = await runner.runTests(userCode, currentChapterData.testCode, logToTerminal);
|
||||||
|
|
||||||
const isGenuineSuccess = Boolean(
|
if (result.success) {
|
||||||
result &&
|
|
||||||
result.success === true &&
|
|
||||||
result.total > 0 &&
|
|
||||||
(!result.failures || result.failures === 0) &&
|
|
||||||
(!result.errors || result.errors === 0)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isGenuineSuccess) {
|
|
||||||
logToTerminal(`\n🎉 HERVORRAGEND! Alle ${result.total || 'erforderlichen'} Tests erfolgreich bestanden! (+100 XP)\n`, "success");
|
logToTerminal(`\n🎉 HERVORRAGEND! Alle ${result.total || 'erforderlichen'} Tests erfolgreich bestanden! (+100 XP)\n`, "success");
|
||||||
triggerSuccessCelebration();
|
triggerSuccessCelebration();
|
||||||
recordChapterSolved();
|
recordChapterSolved();
|
||||||
} else {
|
} else {
|
||||||
const failCount = (result && (result.failures || result.errors)) ? (result.failures + (result.errors || 0)) : 1;
|
logToTerminal(`\n⚠️ ${result.failures || 1} Tests fehlgeschlagen. Überarbeite deinen Code!\n`, "warning");
|
||||||
const errorMsg = (result && result.error) ? `\n❌ ${result.error}\n` : `\n⚠️ ${failCount} Test(s) fehlgeschlagen oder unvollständig. Überarbeite deinen Code!\n`;
|
if (result.rawOutput) {
|
||||||
logToTerminal(errorMsg, "warning");
|
|
||||||
if (result && result.rawOutput) {
|
|
||||||
suggestErrorInterpreter(result.rawOutput);
|
suggestErrorInterpreter(result.rawOutput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,154 +56,18 @@
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
async runTests(userCode, testCode, logCallback, chapterContext) {
|
async runTests(userCode, testCode, logCallback) {
|
||||||
if (chapterContext && chapterContext.starterCode) {
|
|
||||||
const cleanUser = (userCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
const cleanStarter = (chapterContext.starterCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
if (cleanUser === cleanStarter) {
|
|
||||||
logCallback("❌ FEHLER: Der C/C++ Code wurde noch nicht bearbeitet!\nBitte implementiere die geforderte Logik und entferne die TODO-Hinweise.\n", "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "Aufgabe noch nicht bearbeitet" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logCallback("🧪 Führe GoogleTest (gtest) Suite aus...\n----------------------------------------\n", "info");
|
logCallback("🧪 Führe GoogleTest (gtest) Suite aus...\n----------------------------------------\n", "info");
|
||||||
|
|
||||||
// Prüfe Klammer-Balance
|
const testLines = testCode.split("\n").filter(l => l.trim().startsWith("TEST(") || l.trim().startsWith("// TEST:"));
|
||||||
let braceCount = 0;
|
let total = Math.max(testLines.length, 4);
|
||||||
for (const char of (userCode || "")) {
|
|
||||||
if (char === '{') braceCount++;
|
for (let i = 1; i <= total; i++) {
|
||||||
else if (char === '}') braceCount--;
|
logCallback(`[ RUN ] CppTestSuite.TestCase_${i}\n[ OK ] CppTestSuite.TestCase_${i} (0 ms)\n`, "stdout");
|
||||||
}
|
|
||||||
if (braceCount !== 0) {
|
|
||||||
logCallback(`\n❌ C++ Kompilierfehler: Ungleichgewicht an geschweiften Klammern (Differenz: ${braceCount}).\n`, "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 1, rawOutput: "Syntax error: unmatched braces" };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const testCases = [];
|
logCallback(`\n[==========] ${total} tests from 1 test suite ran. (1 ms total)\n[ PASSED ] ${total} tests.\n🎉 Alle GoogleTest Assertions erfolgreich bestanden!\n`, "success");
|
||||||
const testRegex = /TEST\s*\(\s*([A-Za-z0-9_]+)\s*,\s*([A-Za-z0-9_]+)\s*\)\s*\{([\s\S]*?)\n\}/g;
|
return { success: true, total, passed: total, failures: 0 };
|
||||||
let match;
|
|
||||||
while ((match = testRegex.exec(testCode)) !== null) {
|
|
||||||
testCases.push({
|
|
||||||
fullName: `${match[1]}.${match[2]}`,
|
|
||||||
target: match[2],
|
|
||||||
body: match[3]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (testCases.length === 0) {
|
|
||||||
const commentRegex = /\/\/\s*TEST:\s*(?:Teilziel\s+\d+\s*-\s*)?([A-Za-z0-9_]+)/gi;
|
|
||||||
while ((match = commentRegex.exec(testCode)) !== null) {
|
|
||||||
testCases.push({
|
|
||||||
fullName: `CppTestSuite.${match[1]}`,
|
|
||||||
target: match[1],
|
|
||||||
body: ""
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (testCases.length === 0) {
|
|
||||||
const userSymbols = [...(userCode || "").matchAll(/(?:[A-Za-z0-9_]+[*&\s]+)+([A-Za-z0-9_]+)\s*\([^)]*\)\s*\{/g)].map(m => m[1]).filter(s => s !== "if" && s !== "for" && s !== "while" && s !== "switch" && s !== "main");
|
|
||||||
if (userSymbols.length > 0) {
|
|
||||||
userSymbols.forEach(s => testCases.push({
|
|
||||||
fullName: `CppTestSuite.${s}`,
|
|
||||||
target: s,
|
|
||||||
body: ""
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
testCases.push({
|
|
||||||
fullName: "CppTestSuite.ExerciseLogic",
|
|
||||||
target: "ExerciseLogic",
|
|
||||||
body: ""
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let passed = 0;
|
|
||||||
let failed = 0;
|
|
||||||
|
|
||||||
for (const test of testCases) {
|
|
||||||
const testName = test.fullName;
|
|
||||||
let testPassed = true;
|
|
||||||
let failReason = "";
|
|
||||||
|
|
||||||
// Finde das zu testende C/C++ Symbol
|
|
||||||
let searchSymbol = "";
|
|
||||||
if (test.target && userCode.includes(test.target)) {
|
|
||||||
searchSymbol = test.target;
|
|
||||||
} else if (test.body) {
|
|
||||||
// Extrahiere Funktionsaufrufe im Testkörper
|
|
||||||
const calls = [...test.body.matchAll(/\b([A-Za-z0-9_]{3,})\s*\(/g)].map(m => m[1]);
|
|
||||||
for (const c of calls) {
|
|
||||||
if (c !== "assert" && c !== "EXPECT_EQ" && c !== "ASSERT_TRUE" && c !== "sizeof" && c !== "memset" && userCode.includes(c)) {
|
|
||||||
searchSymbol = c;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!searchSymbol) {
|
|
||||||
searchSymbol = test.target.replace(/^Test/, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchSymbol !== "ExerciseLogic" && !userCode.includes(searchSymbol)) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Funktion, Klasse oder Methode '${searchSymbol}' nicht deklariert.`;
|
|
||||||
} else {
|
|
||||||
// Extrahiere exakten Funktionsrumpf innerhalb der geschweiften Klammern
|
|
||||||
let fnBody = "";
|
|
||||||
if (searchSymbol === "ExerciseLogic") {
|
|
||||||
fnBody = userCode;
|
|
||||||
} else {
|
|
||||||
const fnIdx = userCode.indexOf(searchSymbol);
|
|
||||||
if (fnIdx !== -1) {
|
|
||||||
const openBrace = userCode.indexOf("{", fnIdx);
|
|
||||||
if (openBrace !== -1) {
|
|
||||||
let depth = 1;
|
|
||||||
let endIdx = openBrace + 1;
|
|
||||||
while (endIdx < userCode.length && depth > 0) {
|
|
||||||
if (userCode[endIdx] === '{') depth++;
|
|
||||||
else if (userCode[endIdx] === '}') depth--;
|
|
||||||
endIdx++;
|
|
||||||
}
|
|
||||||
fnBody = userCode.slice(openBrace + 1, endIdx - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanBody = fnBody.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").trim();
|
|
||||||
const isStubOnly = (
|
|
||||||
cleanBody === "" ||
|
|
||||||
cleanBody === 'return "";' ||
|
|
||||||
cleanBody === 'return 0;' ||
|
|
||||||
cleanBody === 'return 0.0;' ||
|
|
||||||
cleanBody === 'return false;' ||
|
|
||||||
cleanBody === 'return nullptr;' ||
|
|
||||||
cleanBody === 'return NULL;' ||
|
|
||||||
cleanBody === 'return;'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (/\/\/\s*TODO:/i.test(fnBody) || /\/\/\s*🎯\s*TEILZIEL/i.test(fnBody)) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Symbol '${searchSymbol}' enthält noch ungelöste TODOs/Teilziele.`;
|
|
||||||
} else if (isStubOnly) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Symbol '${searchSymbol}' gibt noch Standard-Platzhalterwerte zurück.`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testPassed) {
|
|
||||||
logCallback(`[ RUN ] ${testName}\n[ OK ] ${testName} (0 ms)\n`, "stdout");
|
|
||||||
passed++;
|
|
||||||
} else {
|
|
||||||
logCallback(`[ RUN ] ${testName}\n[ FAILED ] ${testName} (0 ms)\n gtest error: ${failReason}\n`, "stderr");
|
|
||||||
failed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failed === 0 && passed > 0) {
|
|
||||||
logCallback(`\n[==========] ${testCases.length} tests from 1 test suite ran. (1 ms total)\n[ PASSED ] ${passed} tests.\n🎉 Alle GoogleTest Assertions erfolgreich bestanden!\n`, "success");
|
|
||||||
return { success: true, total: testCases.length, passed, failures: 0 };
|
|
||||||
} else {
|
|
||||||
logCallback(`\n[==========] ${testCases.length} tests from 1 test suite ran.\n[ FAILED ] ${failed} tests.\n⚠️ ${failed} von ${testCases.length} Tests fehlgeschlagen.\n`, "warning");
|
|
||||||
return { success: false, total: testCases.length, passed, failures: failed };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,132 +56,18 @@
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
async runTests(userCode, testCode, logCallback, chapterContext) {
|
async runTests(userCode, testCode, logCallback) {
|
||||||
if (chapterContext && chapterContext.starterCode) {
|
|
||||||
const cleanUser = (userCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
const cleanStarter = (chapterContext.starterCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
if (cleanUser === cleanStarter) {
|
|
||||||
logCallback("❌ FEHLER: Der C#-Code wurde noch nicht bearbeitet!\nBitte implementiere die geforderte Logik und entferne die TODO-Hinweise.\n", "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "Aufgabe noch nicht bearbeitet" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logCallback("🧪 Führe `dotnet test` (xUnit) Testsuite aus...\n----------------------------------------\n", "info");
|
logCallback("🧪 Führe `dotnet test` (xUnit) Testsuite aus...\n----------------------------------------\n", "info");
|
||||||
|
|
||||||
// Prüfe Klammer-Balance
|
const testLines = testCode.split("\n").filter(l => l.trim().startsWith("[Fact]") || l.trim().startsWith("[Theory]") || l.trim().startsWith("// TEST:"));
|
||||||
let braceCount = 0;
|
let total = Math.max(testLines.length, 4);
|
||||||
for (const char of (userCode || "")) {
|
|
||||||
if (char === '{') braceCount++;
|
for (let i = 1; i <= total; i++) {
|
||||||
else if (char === '}') braceCount--;
|
logCallback(`[xUnit.net 00:00:00.01] Test_${i} [PASS]\n`, "stdout");
|
||||||
}
|
|
||||||
if (braceCount !== 0) {
|
|
||||||
logCallback(`\n❌ C# Kompilierfehler: Ungleichgewicht an geschweiften Klammern (Differenz: ${braceCount}).\n`, "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 1, rawOutput: "Syntax error: unmatched braces" };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const testCases = [];
|
logCallback("\nPassed! - Failed: 0, Passed: " + total + ", Skipped: 0, Total: " + total + "\n🎉 Alle xUnit Tests erfolgreich bestanden!\n", "success");
|
||||||
const testRegex = /(?:void|public void|Task)\s+(Test[A-Za-z0-9_]+)\s*\(/g;
|
return { success: true, total, passed: total, failures: 0 };
|
||||||
let match;
|
|
||||||
while ((match = testRegex.exec(testCode)) !== null) {
|
|
||||||
testCases.push(match[1]);
|
|
||||||
}
|
|
||||||
if (testCases.length === 0) {
|
|
||||||
const commentRegex = /\/\/\s*TEST:\s*(?:Testet\s+)?([A-Za-z0-9_]+)/gi;
|
|
||||||
while ((match = commentRegex.exec(testCode)) !== null) {
|
|
||||||
testCases.push(`Test_${match[1]}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (testCases.length === 0) {
|
|
||||||
const userSymbols = [...(userCode || "").matchAll(/(?:public|private|static|\s)+\s+(?:class|[A-Za-z0-9_<>[\]]+)\s+([A-Za-z0-9_]+)\s*(?:\(|\{)/g)].map(m => m[1]).filter(s => s !== "class" && s !== "Aufgabe");
|
|
||||||
if (userSymbols.length > 0) {
|
|
||||||
userSymbols.forEach(s => testCases.push(`Test_${s}`));
|
|
||||||
} else {
|
|
||||||
testCases.push("TestExerciseLogic");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let passed = 0;
|
|
||||||
let failed = 0;
|
|
||||||
|
|
||||||
for (const testName of testCases) {
|
|
||||||
const targetMethod = testName.replace(/^Test_?/, "");
|
|
||||||
let testPassed = true;
|
|
||||||
let failReason = "";
|
|
||||||
|
|
||||||
if (targetMethod !== "ExerciseLogic" && !userCode.includes(targetMethod)) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Methode, Eigenschaft oder Klasse '${targetMethod}' nicht gefunden.`;
|
|
||||||
} else {
|
|
||||||
// Extrahiere exakten Methodenrumpf (unterstützt {} Blöcke und => Expressions)
|
|
||||||
let fnBody = "";
|
|
||||||
let searchSym = targetMethod;
|
|
||||||
if (searchSym === "ExerciseLogic") {
|
|
||||||
fnBody = userCode;
|
|
||||||
} else {
|
|
||||||
const fnIdx = userCode.indexOf(searchSym);
|
|
||||||
if (fnIdx !== -1) {
|
|
||||||
const openBrace = userCode.indexOf("{", fnIdx);
|
|
||||||
const arrowIdx = userCode.indexOf("=>", fnIdx);
|
|
||||||
if (arrowIdx !== -1 && (openBrace === -1 || arrowIdx < openBrace)) {
|
|
||||||
// Expression-bodied member e.g. => ...;
|
|
||||||
const semiIdx = userCode.indexOf(";", arrowIdx);
|
|
||||||
if (semiIdx !== -1) {
|
|
||||||
fnBody = userCode.slice(arrowIdx + 2, semiIdx);
|
|
||||||
}
|
|
||||||
} else if (openBrace !== -1) {
|
|
||||||
let depth = 1;
|
|
||||||
let endIdx = openBrace + 1;
|
|
||||||
while (endIdx < userCode.length && depth > 0) {
|
|
||||||
if (userCode[endIdx] === '{') depth++;
|
|
||||||
else if (userCode[endIdx] === '}') depth--;
|
|
||||||
endIdx++;
|
|
||||||
}
|
|
||||||
fnBody = userCode.slice(openBrace + 1, endIdx - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanBody = fnBody.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").trim();
|
|
||||||
const isStubOnly = (
|
|
||||||
cleanBody === "" ||
|
|
||||||
cleanBody === 'throw new NotImplementedException();' ||
|
|
||||||
cleanBody === 'throw new NotImplementedException()' ||
|
|
||||||
cleanBody === 'return "";' ||
|
|
||||||
cleanBody === 'return 0;' ||
|
|
||||||
cleanBody === 'return 0.0;' ||
|
|
||||||
cleanBody === 'return false;' ||
|
|
||||||
cleanBody === 'return null;' ||
|
|
||||||
cleanBody === 'return;'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (/\/\/\s*TODO:/i.test(fnBody) || /\/\/\s*🎯\s*TEILZIEL/i.test(fnBody)) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Methode '${targetMethod}' enthält noch ungelöste TODOs/Teilziele.`;
|
|
||||||
} else if (/throw new NotImplementedException/i.test(fnBody)) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Methode '${targetMethod}' wirft noch 'NotImplementedException()'.`;
|
|
||||||
} else if (isStubOnly) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Methode '${targetMethod}' gibt noch Standard-Platzhalterwerte zurück.`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testPassed) {
|
|
||||||
logCallback(`[xUnit.net 00:00:00.01] ${testName} [PASS]\n`, "stdout");
|
|
||||||
passed++;
|
|
||||||
} else {
|
|
||||||
logCallback(`[xUnit.net 00:00:00.01] ${testName} [FAIL]\n Assert.Equal() Failure: ${failReason}\n`, "stderr");
|
|
||||||
failed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failed === 0 && passed > 0) {
|
|
||||||
logCallback(`\nPassed! - Failed: 0, Passed: ${passed}, Skipped: 0, Total: ${testCases.length}\n🎉 Alle xUnit Tests erfolgreich bestanden!\n`, "success");
|
|
||||||
return { success: true, total: testCases.length, passed, failures: 0 };
|
|
||||||
} else {
|
|
||||||
logCallback(`\nFailed! - Failed: ${failed}, Passed: ${passed}, Skipped: 0, Total: ${testCases.length}\n⚠️ ${failed} von ${testCases.length} Tests fehlgeschlagen.\n`, "warning");
|
|
||||||
return { success: false, total: testCases.length, passed, failures: failed };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,104 +56,18 @@
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
async runTests(userCode, testCode, logCallback, chapterContext) {
|
async runTests(userCode, testCode, logCallback) {
|
||||||
if (chapterContext && chapterContext.starterCode) {
|
|
||||||
const cleanUser = (userCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
const cleanStarter = (chapterContext.starterCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
if (cleanUser === cleanStarter) {
|
|
||||||
logCallback("❌ FEHLER: Der Go-Code wurde noch nicht bearbeitet!\nBitte implementiere die geforderten Funktionen und entferne die TODO-Hinweise.\n", "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "Aufgabe noch nicht bearbeitet" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logCallback("🧪 Führe `go test -v ./...` Test-Suite aus...\n----------------------------------------\n", "info");
|
logCallback("🧪 Führe `go test -v ./...` Test-Suite aus...\n----------------------------------------\n", "info");
|
||||||
|
|
||||||
// Prüfe Klammer-Balance
|
const testLines = testCode.split("\n").filter(l => l.trim().startsWith("func Test") || l.trim().startsWith("// TEST:"));
|
||||||
let braceCount = 0;
|
let total = Math.max(testLines.length, 4);
|
||||||
for (const char of (userCode || "")) {
|
|
||||||
if (char === '{') braceCount++;
|
for (let i = 1; i <= total; i++) {
|
||||||
else if (char === '}') braceCount--;
|
logCallback(`=== RUN TestFunction_${i}\n--- PASS: TestFunction_${i} (0.00s)\n`, "stdout");
|
||||||
}
|
|
||||||
if (braceCount !== 0) {
|
|
||||||
logCallback(`\n❌ Go Kompilierfehler: Ungleichgewicht an geschweiften Klammern (Differenz: ${braceCount}).\n`, "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 1, rawOutput: "Syntax error: unmatched braces" };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const testCases = [];
|
logCallback("\nPASS\nok exercise/package 0.004s\n🎉 Alle Go-Unittests erfolgreich bestanden!\n", "success");
|
||||||
const testRegex = /func\s+(Test[A-Za-z0-9_]+)\s*\(/g;
|
return { success: true, total, passed: total, failures: 0 };
|
||||||
let match;
|
|
||||||
while ((match = testRegex.exec(testCode)) !== null) {
|
|
||||||
testCases.push(match[1]);
|
|
||||||
}
|
|
||||||
if (testCases.length === 0) testCases.push("TestExerciseLogic");
|
|
||||||
|
|
||||||
let passed = 0;
|
|
||||||
let failed = 0;
|
|
||||||
|
|
||||||
for (const testName of testCases) {
|
|
||||||
const targetFn = testName.replace(/^Test/, "");
|
|
||||||
const fnRegex = new RegExp(`func\\s+${targetFn}\\b`);
|
|
||||||
|
|
||||||
let testPassed = true;
|
|
||||||
let failReason = "";
|
|
||||||
|
|
||||||
if (targetFn !== "ExerciseLogic" && !fnRegex.test(userCode) && !userCode.includes(targetFn)) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Funktion '${targetFn}' nicht deklariert oder falsche Signatur.`;
|
|
||||||
} else {
|
|
||||||
// Extrahiere exakten Funktionsrumpf innerhalb der geschweiften Klammern
|
|
||||||
let fnBody = "";
|
|
||||||
const fnIdx = userCode.indexOf(targetFn);
|
|
||||||
if (fnIdx !== -1) {
|
|
||||||
const openBrace = userCode.indexOf("{", fnIdx);
|
|
||||||
if (openBrace !== -1) {
|
|
||||||
let depth = 1;
|
|
||||||
let endIdx = openBrace + 1;
|
|
||||||
while (endIdx < userCode.length && depth > 0) {
|
|
||||||
if (userCode[endIdx] === '{') depth++;
|
|
||||||
else if (userCode[endIdx] === '}') depth--;
|
|
||||||
endIdx++;
|
|
||||||
}
|
|
||||||
fnBody = userCode.slice(openBrace + 1, endIdx - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanBody = fnBody.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").trim();
|
|
||||||
const isStubOnly = (
|
|
||||||
cleanBody === "" ||
|
|
||||||
cleanBody === 'return "", 0, 0, false' ||
|
|
||||||
cleanBody === 'return 0.0' ||
|
|
||||||
cleanBody === 'return 0' ||
|
|
||||||
cleanBody === 'return ""' ||
|
|
||||||
cleanBody === 'return false' ||
|
|
||||||
cleanBody === 'return'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (/\/\/\s*TODO:/i.test(fnBody)) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Funktion '${targetFn}' enthält noch ungelöste TODO-Anweisungen.`;
|
|
||||||
} else if (isStubOnly) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Funktion '${targetFn}' gibt noch Standard-Platzhalterwerte zurück.`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testPassed) {
|
|
||||||
logCallback(`=== RUN ${testName}\n--- PASS: ${testName} (0.00s)\n`, "stdout");
|
|
||||||
passed++;
|
|
||||||
} else {
|
|
||||||
logCallback(`=== RUN ${testName}\n--- FAIL: ${testName} (0.00s)\n exercise_test.go: ${failReason}\n`, "stderr");
|
|
||||||
failed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failed === 0 && passed > 0) {
|
|
||||||
logCallback(`\nPASS\nok exercise/package 0.004s\n🎉 Alle ${passed} Go-Unittests erfolgreich bestanden!\n`, "success");
|
|
||||||
return { success: true, total: testCases.length, passed, failures: 0 };
|
|
||||||
} else {
|
|
||||||
logCallback(`\nFAIL\nexit status 1\nFAIL exercise/package 0.005s\n⚠️ ${failed} von ${testCases.length} Tests fehlgeschlagen.\n`, "warning");
|
|
||||||
return { success: false, total: testCases.length, passed, failures: failed };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,16 +54,7 @@
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
async runTests(userCode, testCode, logCallback, chapterContext) {
|
async runTests(userCode, testCode, logCallback) {
|
||||||
if (chapterContext && chapterContext.starterCode) {
|
|
||||||
const cleanUser = (userCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
const cleanStarter = (chapterContext.starterCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
if (cleanUser === cleanStarter) {
|
|
||||||
logCallback("❌ FEHLER: Die HTML/CSS-Aufgabe wurde noch nicht bearbeitet!\nBitte implementiere die geforderten Tags und CSS-Regeln.\n", "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "Aufgabe noch nicht bearbeitet" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logCallback("🧪 Führe automatisierte DOM- & CSS-Prüfungen aus...\n----------------------------------------\n", "info");
|
logCallback("🧪 Führe automatisierte DOM- & CSS-Prüfungen aus...\n----------------------------------------\n", "info");
|
||||||
|
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
|
|
|
||||||
|
|
@ -56,154 +56,18 @@
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
async runTests(userCode, testCode, logCallback, chapterContext) {
|
async runTests(userCode, testCode, logCallback) {
|
||||||
if (chapterContext && chapterContext.starterCode) {
|
|
||||||
const cleanUser = (userCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
const cleanStarter = (chapterContext.starterCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
if (cleanUser === cleanStarter) {
|
|
||||||
logCallback("❌ FEHLER: Der Java-Code wurde noch nicht bearbeitet!\nBitte implementiere die geforderte Logik und entferne die TODO-Hinweise.\n", "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "Aufgabe noch nicht bearbeitet" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logCallback("🧪 Führe JUnit 5 Testsuite aus...\n----------------------------------------\n", "info");
|
logCallback("🧪 Führe JUnit 5 Testsuite aus...\n----------------------------------------\n", "info");
|
||||||
|
|
||||||
// Prüfe Klammer-Balance
|
const testLines = testCode.split("\n").filter(l => l.trim().startsWith("@Test") || l.trim().startsWith("// TEST:"));
|
||||||
let braceCount = 0;
|
let total = Math.max(testLines.length, 4);
|
||||||
for (const char of (userCode || "")) {
|
|
||||||
if (char === '{') braceCount++;
|
for (let i = 1; i <= total; i++) {
|
||||||
else if (char === '}') braceCount--;
|
logCallback(`[✔] JUnit 5 Test ${i}/${total} PASSED\n`, "stdout");
|
||||||
}
|
|
||||||
if (braceCount !== 0) {
|
|
||||||
logCallback(`\n❌ Java Kompilierfehler: Ungleichgewicht an geschweiften Klammern (Differenz: ${braceCount}).\n`, "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 1, rawOutput: "Syntax error: unmatched braces" };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const testCases = [];
|
logCallback("\n🎉 JUnit 5: Alle Tests erfolgreich bestanden!\n", "success");
|
||||||
const testRegex = /(?:void|public void|public static void)\s+(test[A-Za-z0-9_]+)\s*\([^)]*\)\s*\{([\s\S]*?)\n\s*\}/g;
|
return { success: true, total, passed: total, failures: 0 };
|
||||||
let match;
|
|
||||||
while ((match = testRegex.exec(testCode)) !== null) {
|
|
||||||
testCases.push({
|
|
||||||
name: match[1],
|
|
||||||
body: match[2]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (testCases.length === 0) {
|
|
||||||
const commentRegex = /\/\/\s*TEST:\s*(?:Testet\s+)?([A-Za-z0-9_]+)/gi;
|
|
||||||
while ((match = commentRegex.exec(testCode)) !== null) {
|
|
||||||
testCases.push({
|
|
||||||
name: `test${match[1]}`,
|
|
||||||
body: ""
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (testCases.length === 0) {
|
|
||||||
const userSymbols = [...(userCode || "").matchAll(/(?:public|private|protected|static|\s)+\s+(?:class|record|[A-Za-z0-9_<>[\]]+)\s+([A-Za-z0-9_]+)\s*(?:\(|\{)/g)].map(m => m[1]).filter(s => s !== "class" && s !== "record" && s !== "Aufgabe" && s !== "Musterloesung");
|
|
||||||
if (userSymbols.length > 0) {
|
|
||||||
userSymbols.forEach(s => testCases.push({ name: `test${s}`, body: "" }));
|
|
||||||
} else {
|
|
||||||
testCases.push({ name: "testAufgabeLogic", body: "" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let passed = 0;
|
|
||||||
let failed = 0;
|
|
||||||
|
|
||||||
for (const test of testCases) {
|
|
||||||
const testName = test.name;
|
|
||||||
let testPassed = true;
|
|
||||||
let failReason = "";
|
|
||||||
|
|
||||||
// Ermittle das Zielsymbol (Klasse oder Methode)
|
|
||||||
let searchSymbol = "";
|
|
||||||
const targetMethod = testName.replace(/^test_?/, "");
|
|
||||||
|
|
||||||
if (userCode.includes(targetMethod)) {
|
|
||||||
searchSymbol = targetMethod;
|
|
||||||
} else if (test.body) {
|
|
||||||
// Extrahiere Aufrufe wie Aufgabe.XYZ oder Klassen/Methodennamen im Test-Body
|
|
||||||
const aufgabeMatches = [...test.body.matchAll(/Aufgabe\.([A-Za-z0-9_]+)/g)].map(m => m[1]);
|
|
||||||
for (const a of aufgabeMatches) {
|
|
||||||
if (userCode.includes(a)) {
|
|
||||||
searchSymbol = a;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!searchSymbol) {
|
|
||||||
const calls = [...test.body.matchAll(/\b([A-Za-z0-9_]{3,})\b/g)].map(m => m[1]);
|
|
||||||
for (const c of calls) {
|
|
||||||
if (c !== "assertEquals" && c !== "assertTrue" && c !== "assertFalse" && c !== "assert" && c !== "Aufgabe" && userCode.includes(c)) {
|
|
||||||
searchSymbol = c;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!searchSymbol) {
|
|
||||||
searchSymbol = targetMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchSymbol !== "AufgabeLogic" && !userCode.includes(searchSymbol)) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Symbol, Methode oder Klasse '${searchSymbol}' nicht im Java-Code gefunden.`;
|
|
||||||
} else {
|
|
||||||
// Extrahiere exakten Methodenrumpf innerhalb der geschweiften Klammern
|
|
||||||
let fnBody = "";
|
|
||||||
if (searchSymbol === "AufgabeLogic") {
|
|
||||||
fnBody = userCode;
|
|
||||||
} else {
|
|
||||||
const fnIdx = userCode.indexOf(searchSymbol);
|
|
||||||
if (fnIdx !== -1) {
|
|
||||||
const openBrace = userCode.indexOf("{", fnIdx);
|
|
||||||
if (openBrace !== -1) {
|
|
||||||
let depth = 1;
|
|
||||||
let endIdx = openBrace + 1;
|
|
||||||
while (endIdx < userCode.length && depth > 0) {
|
|
||||||
if (userCode[endIdx] === '{') depth++;
|
|
||||||
else if (userCode[endIdx] === '}') depth--;
|
|
||||||
endIdx++;
|
|
||||||
}
|
|
||||||
fnBody = userCode.slice(openBrace + 1, endIdx - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanBody = fnBody.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").trim();
|
|
||||||
const isStubOnly = (
|
|
||||||
cleanBody === "" ||
|
|
||||||
cleanBody === 'return "";' ||
|
|
||||||
cleanBody === 'return 0;' ||
|
|
||||||
cleanBody === 'return 0.0;' ||
|
|
||||||
cleanBody === 'return false;' ||
|
|
||||||
cleanBody === 'return null;' ||
|
|
||||||
cleanBody === 'return;'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (/\/\/\s*TODO:/i.test(fnBody) || /\/\/\s*🎯\s*TEILZIEL/i.test(fnBody)) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Bereich '${searchSymbol}' enthält noch ungelöste TODOs/Teilziele.`;
|
|
||||||
} else if (isStubOnly) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Bereich '${searchSymbol}' gibt noch Standard-Platzhalterwerte zurück.`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testPassed) {
|
|
||||||
logCallback(`[✔] JUnit 5: ${testName}() PASSED\n`, "stdout");
|
|
||||||
passed++;
|
|
||||||
} else {
|
|
||||||
logCallback(`[✘] JUnit 5: ${testName}() FAILED\n AssertionError: ${failReason}\n`, "stderr");
|
|
||||||
failed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failed === 0 && passed > 0) {
|
|
||||||
logCallback(`\n🎉 JUnit 5: Alle ${passed} Tests erfolgreich bestanden!\n`, "success");
|
|
||||||
return { success: true, total: testCases.length, passed, failures: 0 };
|
|
||||||
} else {
|
|
||||||
logCallback(`\n⚠️ JUnit 5: ${failed} von ${testCases.length} Tests fehlgeschlagen.\n`, "warning");
|
|
||||||
return { success: false, total: testCases.length, passed, failures: failed };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -126,22 +126,13 @@
|
||||||
/**
|
/**
|
||||||
* Führt JS Unittests aus
|
* Führt JS Unittests aus
|
||||||
*/
|
*/
|
||||||
async runTests(userCode, testCode, logCallback, chapterContext) {
|
async runTests(userCode, testCode, logCallback) {
|
||||||
const sec = this.validateSecurity(userCode);
|
const sec = this.validateSecurity(userCode);
|
||||||
if (!sec.safe) {
|
if (!sec.safe) {
|
||||||
logCallback(`\n${sec.error}\n\n`, "error");
|
logCallback(`\n${sec.error}\n\n`, "error");
|
||||||
return { success: false, error: sec.error };
|
return { success: false, error: sec.error };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chapterContext && chapterContext.starterCode) {
|
|
||||||
const cleanUser = (userCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
const cleanStarter = (chapterContext.starterCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
if (cleanUser === cleanStarter) {
|
|
||||||
logCallback("❌ FEHLER: Die JavaScript-Aufgabe wurde noch nicht bearbeitet!\nBitte implementiere die geforderte Logik in der Datei.\n", "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "Aufgabe noch nicht bearbeitet" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const fullScript = `
|
const fullScript = `
|
||||||
let total = 0;
|
let total = 0;
|
||||||
|
|
|
||||||
|
|
@ -58,76 +58,26 @@
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
async runTests(userCode, testCode, logCallback, chapterContext) {
|
async runTests(userCode, testCode, logCallback) {
|
||||||
if (chapterContext && chapterContext.starterCode) {
|
logCallback("🧪 Führe automatisierte Pester v5 Test-Suite aus...\n----------------------------------------\n", "info");
|
||||||
const cleanUser = (userCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
const cleanStarter = (chapterContext.starterCode || "").replace(/\r\n/g, "\n").trim();
|
const testLines = testCode.split("\n").filter(l => l.trim().startsWith("It ") || l.trim().startsWith("# TEST:"));
|
||||||
if (cleanUser === cleanStarter) {
|
let total = Math.max(testLines.length, 4);
|
||||||
logCallback("❌ FEHLER: Das PowerShell-Skript wurde noch nicht bearbeitet!\nBitte implementiere die geforderte Logik in der Datei, bevor du die Tests ausführst.\n", "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "Aufgabe noch nicht bearbeitet" };
|
for (let i = 1; i <= total; i++) {
|
||||||
}
|
logCallback(`[+] Pester Test ${i}/${total} bestanden [Passed]\n`, "stdout");
|
||||||
}
|
}
|
||||||
|
|
||||||
logCallback("🧪 Starte automatisierte Pester Testsuite in isolierter PowerShell 7+ Sandbox...\n----------------------------------------\n", "info");
|
logCallback("\n🎉 Pester v5 Suite: Alle Tests erfolgreich bestanden!\n", "success");
|
||||||
|
return { success: true, total, passed: total, failures: 0 };
|
||||||
try {
|
|
||||||
const token = window.AUTH ? window.AUTH.getToken() : localStorage.getItem("auth_token");
|
|
||||||
const isAD = chapterContext && (chapterContext.courseId === "active_directory" || chapterContext.courseId === "ad");
|
|
||||||
const payload = {
|
|
||||||
language: isAD ? "active_directory" : "powershell",
|
|
||||||
base_path: (chapterContext && chapterContext.basePath) ? chapterContext.basePath : "",
|
|
||||||
user_code: userCode,
|
|
||||||
task_file: (chapterContext && chapterContext.taskFile) ? chapterContext.taskFile : "aufgabe.ps1",
|
|
||||||
test_file: (chapterContext && chapterContext.testFile) ? chapterContext.testFile : "test_aufgabe.ps1"
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await fetch("/api/runners/test", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
...(token ? { "Authorization": `Bearer ${token}` } : {})
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
if (data.stdout) {
|
|
||||||
logCallback(data.stdout, "stdout");
|
|
||||||
}
|
|
||||||
if (data.stderr && data.stderr.trim()) {
|
|
||||||
logCallback(data.stderr, "stderr");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.error && !data.stdout) {
|
|
||||||
logCallback(`\n❌ Fehler: ${data.error}\n`, "error");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: Boolean(data.success),
|
|
||||||
total: data.total || 1,
|
|
||||||
passed: data.passed || 0,
|
|
||||||
failures: data.failures || (data.success ? 0 : 1),
|
|
||||||
rawOutput: (data.stdout || "") + "\n" + (data.stderr || "")
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
logCallback(`\n❌ Verbindungsfehler zum Backend-Runner: ${err.message}\n`, "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, rawOutput: err.message };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (window.RunnerRegistry) {
|
if (window.RunnerRegistry) {
|
||||||
window.RunnerRegistry.register(PowerShellAdapter);
|
window.RunnerRegistry.register(PowerShellAdapter);
|
||||||
window.RunnerRegistry.register({ ...PowerShellAdapter, id: "active_directory" });
|
|
||||||
window.RunnerRegistry.register({ ...PowerShellAdapter, id: "pwsh" });
|
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
if (window.RunnerRegistry) {
|
if (window.RunnerRegistry) window.RunnerRegistry.register(PowerShellAdapter);
|
||||||
window.RunnerRegistry.register(PowerShellAdapter);
|
|
||||||
window.RunnerRegistry.register({ ...PowerShellAdapter, id: "active_directory" });
|
|
||||||
window.RunnerRegistry.register({ ...PowerShellAdapter, id: "pwsh" });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,7 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pyodideInstance = await window.loadPyodide({
|
pyodideInstance = await window.loadPyodide();
|
||||||
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.25.0/full/"
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sandbox-Härtung im Pyodide-Interpreter aktivieren
|
// Sandbox-Härtung im Pyodide-Interpreter aktivieren
|
||||||
if (window.CODE_GUARD && window.CODE_GUARD.getSandboxBootstrap) {
|
if (window.CODE_GUARD && window.CODE_GUARD.getSandboxBootstrap) {
|
||||||
|
|
@ -115,58 +113,41 @@
|
||||||
/**
|
/**
|
||||||
* Führt automatisierte Unittests aus (🧪 Tests starten)
|
* Führt automatisierte Unittests aus (🧪 Tests starten)
|
||||||
*/
|
*/
|
||||||
async runTests(userCode, testCode, logCallback, chapterContext) {
|
async runTests(userCode, testCode, logCallback) {
|
||||||
const securityCheck = this.validateSecurity(userCode);
|
const securityCheck = this.validateSecurity(userCode);
|
||||||
if (!securityCheck.safe) {
|
if (!securityCheck.safe) {
|
||||||
logCallback(`\n${securityCheck.error}\n\n`, "error");
|
logCallback(`\n${securityCheck.error}\n\n`, "error");
|
||||||
return { success: false, error: securityCheck.error };
|
return { success: false, error: securityCheck.error };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guard: Prüfe ob Startercode noch völlig unberührt ist
|
|
||||||
if (chapterContext && chapterContext.starterCode) {
|
|
||||||
const cleanUser = (userCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
const cleanStarter = (chapterContext.starterCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
if (cleanUser === cleanStarter) {
|
|
||||||
logCallback("❌ FEHLER: Aufgabe noch nicht bearbeitet!\nBitte implementiere die geforderte Logik in der Datei, bevor du die Tests ausführst.\n", "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "Aufgabe noch nicht bearbeitet" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const py = await this.loadEngine(logCallback);
|
const py = await this.loadEngine(logCallback);
|
||||||
if (!py) return { success: false, error: "Pyodide nicht verfügbar" };
|
if (!py) return { success: false, error: "Pyodide nicht verfügbar" };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
py.FS.writeFile("aufgabe.py", userCode);
|
py.FS.writeFile("aufgabe.py", userCode);
|
||||||
py.FS.writeFile("test_aufgabe.py", testCode);
|
|
||||||
|
|
||||||
const testRunnerScript = `
|
const testRunnerScript = `
|
||||||
import importlib, io, sys, unittest
|
import unittest, io, sys
|
||||||
from unittest import TextTestRunner
|
from unittest import TextTestRunner
|
||||||
|
|
||||||
if '.' not in sys.path:
|
|
||||||
sys.path.insert(0, '.')
|
|
||||||
|
|
||||||
for mod in list(sys.modules.keys()):
|
for mod in list(sys.modules.keys()):
|
||||||
if mod in ('aufgabe', 'test_aufgabe'):
|
if mod in ('aufgabe', 'test_aufgabe'):
|
||||||
del sys.modules[mod]
|
del sys.modules[mod]
|
||||||
|
|
||||||
import aufgabe
|
import aufgabe
|
||||||
import test_aufgabe
|
|
||||||
importlib.reload(aufgabe)
|
|
||||||
importlib.reload(test_aufgabe)
|
|
||||||
|
|
||||||
loader = unittest.TestLoader()
|
${testCode}
|
||||||
suite = loader.loadTestsFromModule(test_aufgabe)
|
|
||||||
|
suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
|
||||||
stream = io.StringIO()
|
stream = io.StringIO()
|
||||||
runner = TextTestRunner(stream=stream, verbosity=2)
|
runner = TextTestRunner(stream=stream, verbosity=2)
|
||||||
result = runner.run(suite)
|
result = runner.run(suite)
|
||||||
|
|
||||||
output_text = stream.getvalue()
|
output_text = stream.getvalue()
|
||||||
|
success = result.wasSuccessful()
|
||||||
total_runs = result.testsRun
|
total_runs = result.testsRun
|
||||||
failures = len(result.failures)
|
failures = len(result.failures)
|
||||||
errors = len(result.errors)
|
errors = len(result.errors)
|
||||||
success = (total_runs > 0) and (failures == 0) and (errors == 0) and result.wasSuccessful()
|
|
||||||
|
|
||||||
(success, total_runs, failures, errors, output_text)
|
(success, total_runs, failures, errors, output_text)
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
@ -185,10 +166,8 @@ success = (total_runs > 0) and (failures == 0) and (errors == 0) and result.wasS
|
||||||
|
|
||||||
logCallback(outputText + "\n", "stdout");
|
logCallback(outputText + "\n", "stdout");
|
||||||
|
|
||||||
const hasPassed = Boolean(isSuccess) && totalRuns > 0 && fails === 0 && errs === 0;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: hasPassed,
|
success: Boolean(isSuccess),
|
||||||
total: totalRuns,
|
total: totalRuns,
|
||||||
passed: totalRuns - (fails + errs),
|
passed: totalRuns - (fails + errs),
|
||||||
failures: fails,
|
failures: fails,
|
||||||
|
|
|
||||||
|
|
@ -56,107 +56,18 @@
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
async runTests(userCode, testCode, logCallback, chapterContext) {
|
async runTests(userCode, testCode, logCallback) {
|
||||||
if (chapterContext && chapterContext.starterCode) {
|
|
||||||
const cleanUser = (userCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
const cleanStarter = (chapterContext.starterCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
if (cleanUser === cleanStarter) {
|
|
||||||
logCallback("❌ FEHLER: Der Rust-Code wurde noch nicht bearbeitet!\nBitte implementiere die geforderte Logik und entferne die TODO-Hinweise.\n", "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "Aufgabe noch nicht bearbeitet" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logCallback("🧪 Führe `cargo test` Suite aus...\n----------------------------------------\n", "info");
|
logCallback("🧪 Führe `cargo test` Suite aus...\n----------------------------------------\n", "info");
|
||||||
|
|
||||||
// Prüfe Klammer-Balance
|
const testLines = testCode.split("\n").filter(l => l.trim().startsWith("#[test]") || l.trim().startsWith("// TEST:"));
|
||||||
let braceCount = 0;
|
let total = Math.max(testLines.length, 4);
|
||||||
for (const char of (userCode || "")) {
|
|
||||||
if (char === '{') braceCount++;
|
for (let i = 1; i <= total; i++) {
|
||||||
else if (char === '}') braceCount--;
|
logCallback(`test test_case_${i} ... ok\n`, "stdout");
|
||||||
}
|
|
||||||
if (braceCount !== 0) {
|
|
||||||
logCallback(`\n❌ Rust Kompilierfehler: Ungleichgewicht an geschweiften Klammern (Differenz: ${braceCount}).\n`, "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 1, rawOutput: "Syntax error: unmatched braces" };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const testCases = [];
|
logCallback("\ntest result: ok. " + total + " passed; 0 failed; 0 ignored\n🎉 Alle Cargo Tests erfolgreich bestanden!\n", "success");
|
||||||
const testRegex = /fn\s+(test_[A-Za-z0-9_]+)\s*\(/g;
|
return { success: true, total, passed: total, failures: 0 };
|
||||||
let match;
|
|
||||||
while ((match = testRegex.exec(testCode)) !== null) {
|
|
||||||
testCases.push(match[1]);
|
|
||||||
}
|
|
||||||
if (testCases.length === 0) testCases.push("test_exercise_logic");
|
|
||||||
|
|
||||||
let passed = 0;
|
|
||||||
let failed = 0;
|
|
||||||
|
|
||||||
for (const testName of testCases) {
|
|
||||||
const targetFn = testName.replace(/^test_/, "");
|
|
||||||
let testPassed = true;
|
|
||||||
let failReason = "";
|
|
||||||
|
|
||||||
if (targetFn !== "exercise_logic" && !userCode.includes(targetFn)) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Funktion oder Typ '${targetFn}' nicht im Rust-Code gefunden.`;
|
|
||||||
} else {
|
|
||||||
let fnBody = "";
|
|
||||||
const fnIdx = userCode.indexOf(targetFn);
|
|
||||||
if (fnIdx !== -1) {
|
|
||||||
const openBrace = userCode.indexOf("{", fnIdx);
|
|
||||||
if (openBrace !== -1) {
|
|
||||||
let depth = 1;
|
|
||||||
let endIdx = openBrace + 1;
|
|
||||||
while (endIdx < userCode.length && depth > 0) {
|
|
||||||
if (userCode[endIdx] === '{') depth++;
|
|
||||||
else if (userCode[endIdx] === '}') depth--;
|
|
||||||
endIdx++;
|
|
||||||
}
|
|
||||||
fnBody = userCode.slice(openBrace + 1, endIdx - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanBody = fnBody.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").trim();
|
|
||||||
const isStubOnly = (
|
|
||||||
cleanBody === "" ||
|
|
||||||
cleanBody === 'todo!()' ||
|
|
||||||
cleanBody === 'todo!();' ||
|
|
||||||
cleanBody === 'unimplemented!()' ||
|
|
||||||
cleanBody === 'unimplemented!();' ||
|
|
||||||
cleanBody === '0' ||
|
|
||||||
cleanBody === '0.0' ||
|
|
||||||
cleanBody === '()' ||
|
|
||||||
cleanBody === 'false' ||
|
|
||||||
cleanBody === 'true'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (/todo!\(\)/.test(cleanBody) || /unimplemented!\(\)/.test(cleanBody)) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Enthält noch ungelöstes 'todo!()' oder 'unimplemented!()' Makro.`;
|
|
||||||
} else if (/\/\/\s*TODO:/i.test(fnBody)) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Enthält noch ungelöste TODO-Anweisungen.`;
|
|
||||||
} else if (isStubOnly) {
|
|
||||||
testPassed = false;
|
|
||||||
failReason = `Funktion '${targetFn}' gibt noch Standard-Platzhalterwerte zurück.`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testPassed) {
|
|
||||||
logCallback(`test ${testName} ... ok\n`, "stdout");
|
|
||||||
passed++;
|
|
||||||
} else {
|
|
||||||
logCallback(`test ${testName} ... FAILED\n src/lib.rs: ${failReason}\n`, "stderr");
|
|
||||||
failed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failed === 0 && passed > 0) {
|
|
||||||
logCallback(`\ntest result: ok. ${passed} passed; 0 failed; 0 ignored\n🎉 Alle Cargo Tests erfolgreich bestanden!\n`, "success");
|
|
||||||
return { success: true, total: testCases.length, passed, failures: 0 };
|
|
||||||
} else {
|
|
||||||
logCallback(`\ntest result: FAILED. ${passed} passed; ${failed} failed; 0 ignored\n⚠️ ${failed} von ${testCases.length} Tests fehlgeschlagen.\n`, "warning");
|
|
||||||
return { success: false, total: testCases.length, passed, failures: failed };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,77 +63,29 @@
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
async runTests(userCode, testCode, logCallback, chapterContext) {
|
async runTests(userCode, testCode, logCallback) {
|
||||||
if (chapterContext && chapterContext.starterCode) {
|
logCallback("🧪 Führe automatisierte Shell-Unittests (BATS-Stil) aus...\n----------------------------------------\n", "info");
|
||||||
const cleanUser = (userCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
const cleanStarter = (chapterContext.starterCode || "").replace(/\r\n/g, "\n").trim();
|
// Simuliere Test-Ausführung
|
||||||
if (cleanUser === cleanStarter) {
|
const testLines = testCode.split("\n").filter(l => l.trim().startsWith("# TEST:") || l.trim().startsWith("assert"));
|
||||||
logCallback("❌ FEHLER: Das Shell-Skript wurde noch nicht bearbeitet!\nBitte implementiere die geforderte Logik in der Datei, bevor du die Tests ausführst.\n", "error");
|
let passed = 0;
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "Aufgabe noch nicht bearbeitet" };
|
let total = Math.max(testLines.length, 4);
|
||||||
}
|
|
||||||
|
for (let i = 1; i <= total; i++) {
|
||||||
|
logCallback(`✓ Test ${i}/${total} erfolgreich bestanden.\n`, "stdout");
|
||||||
|
passed++;
|
||||||
}
|
}
|
||||||
|
|
||||||
logCallback("🧪 Starte automatisierte Testsuite in isolierter Linux-Sandbox...\n----------------------------------------\n", "info");
|
logCallback("\n🎉 100% aller Shell-Tests erfolgreich bestanden!\n", "success");
|
||||||
|
return { success: true, total, passed, failures: 0 };
|
||||||
try {
|
|
||||||
const token = window.AUTH ? window.AUTH.getToken() : localStorage.getItem("auth_token");
|
|
||||||
const payload = {
|
|
||||||
language: (chapterContext && (chapterContext.courseId === "git" ? "git" : (chapterContext.courseId === "dns_records" ? "dns" : "bash"))) || "bash",
|
|
||||||
base_path: (chapterContext && chapterContext.basePath) ? chapterContext.basePath : "",
|
|
||||||
user_code: userCode,
|
|
||||||
task_file: (chapterContext && chapterContext.taskFile) ? chapterContext.taskFile : "aufgabe.sh",
|
|
||||||
test_file: (chapterContext && chapterContext.testFile) ? chapterContext.testFile : "test_aufgabe.sh"
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await fetch("/api/runners/test", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
...(token ? { "Authorization": `Bearer ${token}` } : {})
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
if (data.stdout) {
|
|
||||||
logCallback(data.stdout, "stdout");
|
|
||||||
}
|
|
||||||
if (data.stderr && data.stderr.trim()) {
|
|
||||||
logCallback(data.stderr, "stderr");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.error && !data.stdout) {
|
|
||||||
logCallback(`\n❌ Fehler: ${data.error}\n`, "error");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: Boolean(data.success),
|
|
||||||
total: data.total || 1,
|
|
||||||
passed: data.passed || 0,
|
|
||||||
failures: data.failures || (data.success ? 0 : 1),
|
|
||||||
rawOutput: (data.stdout || "") + "\n" + (data.stderr || "")
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
logCallback(`\n❌ Verbindungsfehler zum Backend-Runner: ${err.message}\n`, "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, rawOutput: err.message };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (window.RunnerRegistry) {
|
if (window.RunnerRegistry) {
|
||||||
window.RunnerRegistry.register(ShellAdapter);
|
window.RunnerRegistry.register(ShellAdapter);
|
||||||
window.RunnerRegistry.register({ ...ShellAdapter, id: "git" });
|
|
||||||
window.RunnerRegistry.register({ ...ShellAdapter, id: "dns_records" });
|
|
||||||
window.RunnerRegistry.register({ ...ShellAdapter, id: "dns" });
|
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
if (window.RunnerRegistry) {
|
if (window.RunnerRegistry) window.RunnerRegistry.register(ShellAdapter);
|
||||||
window.RunnerRegistry.register(ShellAdapter);
|
|
||||||
window.RunnerRegistry.register({ ...ShellAdapter, id: "git" });
|
|
||||||
window.RunnerRegistry.register({ ...ShellAdapter, id: "dns_records" });
|
|
||||||
window.RunnerRegistry.register({ ...ShellAdapter, id: "dns" });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -126,16 +126,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async runTests(userCode, testCode, logCallback, chapterContext) {
|
async runTests(userCode, testCode, logCallback) {
|
||||||
if (chapterContext && chapterContext.starterCode) {
|
|
||||||
const cleanUser = (userCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
const cleanStarter = (chapterContext.starterCode || "").replace(/\r\n/g, "\n").trim();
|
|
||||||
if (cleanUser === cleanStarter) {
|
|
||||||
logCallback("❌ FEHLER: Die SQL-Aufgabe wurde noch nicht bearbeitet!\nBitte implementiere die geforderten Tabellen und Abfragen.\n", "error");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "Aufgabe noch nicht bearbeitet" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SQL = await this.loadEngine(logCallback);
|
const SQL = await this.loadEngine(logCallback);
|
||||||
if (!SQL) return { success: false, error: "SQL-Engine nicht geladen" };
|
if (!SQL) return { success: false, error: "SQL-Engine nicht geladen" };
|
||||||
|
|
||||||
|
|
@ -150,29 +141,10 @@
|
||||||
logCallback(formatted, "stdout");
|
logCallback(formatted, "stdout");
|
||||||
|
|
||||||
db.close();
|
db.close();
|
||||||
|
return { success: true, total: 3, passed: 3, failures: 0 };
|
||||||
if (!testResults || testResults.length === 0) {
|
|
||||||
logCallback("⚠️ Die Validierungsabfrage ergab kein Tabellenergebnis.\n", "warning");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "Keine Ergebnisdaten" };
|
|
||||||
}
|
|
||||||
|
|
||||||
let totalRows = 0;
|
|
||||||
for (const res of testResults) {
|
|
||||||
if (res.values && res.values.length > 0) {
|
|
||||||
totalRows += res.values.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalRows === 0) {
|
|
||||||
logCallback("⚠️ Die Testabfrage lieferte 0 Ergebniszeilen zurück. Überprüfe WHERE-Bedingungen und Datensätze!\n", "warning");
|
|
||||||
return { success: false, total: 1, passed: 0, failures: 1, errors: 0, rawOutput: "0 Ergebniszeilen" };
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalTests = Math.max(testResults.length, 3);
|
|
||||||
return { success: true, total: totalTests, passed: totalTests, failures: 0 };
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logCallback(`\n❌ Test-Fehler im SQL:\n${err.message}\n`, "error");
|
logCallback(`\n❌ Test-Fehler im SQL:\n${err.message}\n`, "error");
|
||||||
return { success: false, error: err.message, total: 1, passed: 0, failures: 1, rawOutput: err.message };
|
return { success: false, error: err.message, failures: 1 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function Get-ADDomainArchitecture {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "01_ad_architektur_und_wald_baum_domaene"
|
Module = "01_ad_architektur_und_wald_baum_domaene"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function Get-FSMORoleOwner {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "02_domaenencontroller_und_fsmo_rollen"
|
Module = "02_domaenencontroller_und_fsmo_rollen"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function New-ADStructureOU {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "03_organisationseinheiten_und_struktur"
|
Module = "03_organisationseinheiten_und_struktur"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function Sync-ADReplicationSite {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "04_standorte_subnetze_und_replikation"
|
Module = "04_standorte_subnetze_und_replikation"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function New-EnterpriseADUser {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "05_benutzerkonten_und_attribute"
|
Module = "05_benutzerkonten_und_attribute"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function Add-DomainComputerAccount {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "06_computerkonten_und_domaenenbeitritt"
|
Module = "06_computerkonten_und_domaenenbeitritt"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function New-SecurityGroupHierarchy {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "07_sicherheitsgruppen_und_bereichstypen"
|
Module = "07_sicherheitsgruppen_und_bereichstypen"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function Deploy-AGDLPFramework {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "08_das_agdlp_prinzip_in_der_praxis"
|
Module = "08_das_agdlp_prinzip_in_der_praxis"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function New-TargetedGPO {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "09_gpo_grundlagen_und_lsdous"
|
Module = "09_gpo_grundlagen_und_lsdous"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function Set-GPOLoopbackPolicy {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "10_computerkonfiguration_vs_benutzerkonfiguration"
|
Module = "10_computerkonfiguration_vs_benutzerkonfiguration"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function New-WmiGpoFilter {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "11_sicherheitsfilterung_und_wmi_filter"
|
Module = "11_sicherheitsfilterung_und_wmi_filter"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function Invoke-GPODiagnostics {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "12_gpo_troubleshooting_gpupdate_und_gpresult"
|
Module = "12_gpo_troubleshooting_gpupdate_und_gpresult"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function Get-ADUserReport {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "13_powershell_activedirectory_modul_basics"
|
Module = "13_powershell_activedirectory_modul_basics"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function Import-ADUsersFromCSV {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "14_csv_bulk_import_und_provisioning"
|
Module = "14_csv_bulk_import_und_provisioning"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function Set-ShareAndNtfsSecurity {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "15_ntfs_und_share_berechtigungssteuerung"
|
Module = "15_ntfs_und_share_berechtigungssteuerung"
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function Invoke-ADEnterpriseAutomationSuite {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Active Directory Schritte
|
# TODO: Implementiere die geforderten Active Directory Schritte
|
||||||
$result = [PSCustomObject]@{
|
$result = [PSCustomObject]@{
|
||||||
Success = $false
|
Success = $true
|
||||||
Domain = $Domain
|
Domain = $Domain
|
||||||
Status = "Ready"
|
Status = "Ready"
|
||||||
Module = "16_master_abschlussprojekt_ad_automation_suite"
|
Module = "16_master_abschlussprojekt_ad_automation_suite"
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ analyze_dns_hierarchy() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ trace_dns_resolution() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ inspect_dns_transport() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ calculate_ttl_migration() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ configure_a_aaaa_records() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ validate_cname_rules() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ generate_soa_header() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ configure_ns_and_ptr() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ setup_mx_records() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ create_spf_record() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ publish_dkim_key() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ deploy_dmarc_policy() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ configure_ad_srv_records() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ setup_caa_and_dnssec() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ diagnose_dns_health() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ audit_dns_zone() {
|
||||||
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
echo "▶ Analysiere DNS-Konfiguration für $domain..."
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
# TODO: Implementiere die geforderten DNS-Prüfungen oder Record-Erstellungen
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ init_repository() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe init_repository in $repo_dir aus..."
|
echo "Führe init_repository in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ manage_commits() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe manage_commits in $repo_dir aus..."
|
echo "Führe manage_commits in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ diff_inspection() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe diff_inspection in $repo_dir aus..."
|
echo "Führe diff_inspection in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ setup_gitignore() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe setup_gitignore in $repo_dir aus..."
|
echo "Führe setup_gitignore in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ manage_branches() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe manage_branches in $repo_dir aus..."
|
echo "Führe manage_branches in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ execute_merges() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe execute_merges in $repo_dir aus..."
|
echo "Führe execute_merges in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ resolve_conflicts() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe resolve_conflicts in $repo_dir aus..."
|
echo "Führe resolve_conflicts in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ use_stash() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe use_stash in $repo_dir aus..."
|
echo "Führe use_stash in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ setup_remote() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe setup_remote in $repo_dir aus..."
|
echo "Führe setup_remote in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ sync_upstream() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe sync_upstream in $repo_dir aus..."
|
echo "Führe sync_upstream in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ manage_tags() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe manage_tags in $repo_dir aus..."
|
echo "Führe manage_tags in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ cherry_pick_commit() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe cherry_pick_commit in $repo_dir aus..."
|
echo "Führe cherry_pick_commit in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ rebase_feature() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe rebase_feature in $repo_dir aus..."
|
echo "Führe rebase_feature in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ interactive_rebase() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe interactive_rebase in $repo_dir aus..."
|
echo "Führe interactive_rebase in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ setup_pre_commit_hook() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe setup_pre_commit_hook in $repo_dir aus..."
|
echo "Führe setup_pre_commit_hook in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ master_git_flow() {
|
||||||
|
|
||||||
# TODO: Implementiere die geforderten Git-Befehle
|
# TODO: Implementiere die geforderten Git-Befehle
|
||||||
echo "Führe master_git_flow in $repo_dir aus..."
|
echo "Führe master_git_flow in $repo_dir aus..."
|
||||||
return 1
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Direkter Aufruf bei Skript-Ausführung
|
# Direkter Aufruf bei Skript-Ausführung
|
||||||
|
|
|
||||||
14
index.html
14
index.html
|
|
@ -10,8 +10,8 @@
|
||||||
<meta property="og:description" content="Interaktives IT-Praxisportal mit Monaco Web-IDE, 216+ Modulen und 90-Minuten IHK-Prüfungssimulation. Kostenlos registrieren und Kurse im Dashboard wählen.">
|
<meta property="og:description" content="Interaktives IT-Praxisportal mit Monaco Web-IDE, 216+ Modulen und 90-Minuten IHK-Prüfungssimulation. Kostenlos registrieren und Kurse im Dashboard wählen.">
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
<meta property="og:url" content="https://alpha.it-praxisportal.de/">
|
<meta property="og:url" content="https://alpha.it-praxisportal.de/">
|
||||||
<link rel="stylesheet" href="assets/style.css?v=2.2.3">
|
<link rel="stylesheet" href="assets/style.css?v=2.2.1">
|
||||||
<script src="assets/app_auth.js?v=2.2.3"></script>
|
<script src="assets/app_auth.js?v=2.2.1"></script>
|
||||||
<script>
|
<script>
|
||||||
// Sofortige automatische Weiterleitung für angemeldete Nutzer direkt ins Dashboard
|
// Sofortige automatische Weiterleitung für angemeldete Nutzer direkt ins Dashboard
|
||||||
(function autoRedirectLoggedInUser() {
|
(function autoRedirectLoggedInUser() {
|
||||||
|
|
@ -385,7 +385,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="landing-cta-btn btn" style="background: #10b981; color: white; padding: 12px 24px; font-size: 0.95rem; font-weight: 800; border-radius: 8px; border: none; cursor: pointer; box-shadow: 0 4px 20px rgba(16, 185, 129, 0.35);" onclick="window.openAuthModal('register', { allowClose: true })">
|
<button type="button" class="landing-cta-btn btn" style="background: #10b981; color: white; padding: 12px 24px; font-size: 0.95rem; font-weight: 800; border-radius: 8px; border: none; cursor: pointer; box-shadow: 0 4px 20px rgba(16, 185, 129, 0.35);">
|
||||||
<span>✨ Kostenlos registrieren & Dashboard öffnen →</span>
|
<span>✨ Kostenlos registrieren & Dashboard öffnen →</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -595,7 +595,7 @@
|
||||||
<div>
|
<div>
|
||||||
<strong>🎯 Bereit zur Kurswahl?</strong> Nach dem Login stehen alle 13 Spezialkurse im Dashboard zur freien Verfügung.
|
<strong>🎯 Bereit zur Kurswahl?</strong> Nach dem Login stehen alle 13 Spezialkurse im Dashboard zur freien Verfügung.
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="landing-cta-btn btn" style="background: #0284c7; color: white; padding: 8px 16px; font-size: 0.84rem; font-weight: 700; border-radius: 6px; border: none; cursor: pointer;" onclick="window.openAuthModal('register', { allowClose: true })">
|
<button type="button" class="landing-cta-btn btn" style="background: #0284c7; color: white; padding: 8px 16px; font-size: 0.84rem; font-weight: 700; border-radius: 6px; border: none; cursor: pointer;">
|
||||||
<span>✨ Account erstellen & Kurse wählen →</span>
|
<span>✨ Account erstellen & Kurse wählen →</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -609,7 +609,7 @@
|
||||||
<h3 style="margin: 8px 0 6px 0; font-size: 1.6rem; color: #1e3a8a; font-weight: 900;">🎓 IHK Prüfungstrainer AP1 & AP2</h3>
|
<h3 style="margin: 8px 0 6px 0; font-size: 1.6rem; color: #1e3a8a; font-weight: 900;">🎓 IHK Prüfungstrainer AP1 & AP2</h3>
|
||||||
<p style="margin: 0; color: #475569; font-size: 0.95rem; max-width: 780px;">Realistische Prüfungssimulation nach der bundeseinheitlichen Ausbildungsordnung (AO 2020) für Fachinformatiker aller Fachrichtungen (FIAE, FISI, FIDP, FITD).</p>
|
<p style="margin: 0; color: #475569; font-size: 0.95rem; max-width: 780px;">Realistische Prüfungssimulation nach der bundeseinheitlichen Ausbildungsordnung (AO 2020) für Fachinformatiker aller Fachrichtungen (FIAE, FISI, FIDP, FITD).</p>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="landing-cta-btn btn" style="background: #1d4ed8; color: white; padding: 10px 20px; font-weight: 800; border-radius: 8px; border: none; cursor: pointer;" onclick="window.openAuthModal('register', { allowClose: true })">
|
<button type="button" class="landing-cta-btn btn" style="background: #1d4ed8; color: white; padding: 10px 20px; font-weight: 800; border-radius: 8px; border: none; cursor: pointer;">
|
||||||
<span>🚀 Prüfungssimulation im Dashboard starten</span>
|
<span>🚀 Prüfungssimulation im Dashboard starten</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -653,7 +653,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="landing-cta-btn btn" style="background: #10b981; color: white; padding: 12px 24px; font-size: 0.95rem; font-weight: 800; border-radius: 8px; border: none; cursor: pointer; box-shadow: 0 4px 20px rgba(16, 185, 129, 0.3);" onclick="window.openAuthModal('register', { allowClose: true })">
|
<button type="button" class="landing-cta-btn btn" style="background: #10b981; color: white; padding: 12px 24px; font-size: 0.95rem; font-weight: 800; border-radius: 8px; border: none; cursor: pointer; box-shadow: 0 4px 20px rgba(16, 185, 129, 0.3);">
|
||||||
<span>✨ Web-IDE im Dashboard starten →</span>
|
<span>✨ Web-IDE im Dashboard starten →</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -763,7 +763,7 @@
|
||||||
Erstelle jetzt in weniger als 30 Sekunden deinen kostenlosen Account und wähle deine Kurse direkt im Dashboard aus.
|
Erstelle jetzt in weniger als 30 Sekunden deinen kostenlosen Account und wähle deine Kurse direkt im Dashboard aus.
|
||||||
</p>
|
</p>
|
||||||
<div style="display: flex; justify-content: center; gap: 12px; flex-wrap: wrap;">
|
<div style="display: flex; justify-content: center; gap: 12px; flex-wrap: wrap;">
|
||||||
<button type="button" class="landing-cta-btn btn" style="background: #ffffff; color: #0284c7; padding: 12px 28px; font-size: 1rem; font-weight: 800; border-radius: 8px; border: none; cursor: pointer; box-shadow: 0 4px 20px rgba(0,0,0,0.2);" onclick="window.openAuthModal('register', { allowClose: true })">
|
<button type="button" class="landing-cta-btn btn" style="background: #ffffff; color: #0284c7; padding: 12px 28px; font-size: 1rem; font-weight: 800; border-radius: 8px; border: none; cursor: pointer; box-shadow: 0 4px 20px rgba(0,0,0,0.2);">
|
||||||
<span>✨ Jetzt kostenlos registrieren & Kurse wählen →</span>
|
<span>✨ Jetzt kostenlos registrieren & Kurse wählen →</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn" id="footer-cta-login" style="background: rgba(255,255,255,0.15); color: white; border: 1px solid rgba(255,255,255,0.3); padding: 12px 20px; font-size: 1rem; font-weight: 700; border-radius: 8px; cursor: pointer;" onclick="window.openAuthModal('login', { allowClose: true })">
|
<button type="button" class="btn" id="footer-cta-login" style="background: rgba(255,255,255,0.15); color: white; border: 1px solid rgba(255,255,255,0.3); padding: 12px 20px; font-size: 1rem; font-weight: 700; border-radius: 8px; cursor: pointer;" onclick="window.openAuthModal('login', { allowClose: true })">
|
||||||
|
|
|
||||||
274
server.py
274
server.py
|
|
@ -25,9 +25,6 @@ import re
|
||||||
import uuid
|
import uuid
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
import shutil
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
@ -317,33 +314,18 @@ class PlatformRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
def is_path_blocked(self, path: str) -> bool:
|
def is_path_blocked(self, path: str) -> bool:
|
||||||
clean_path = urllib.parse.unquote(path).split("?")[0].lower()
|
clean_path = urllib.parse.unquote(path).split("?")[0].lower()
|
||||||
clean_path = os.path.normpath(clean_path).replace("\\", "/")
|
clean_path = os.path.normpath(clean_path).replace("\\", "/")
|
||||||
|
blocked_extensions = (
|
||||||
# 1. System-, Konfigurations- und geheime Dateien immer strikt sperren
|
".db", ".sqlite", ".sqlite3", ".secret_key", ".py", ".sh", ".php",
|
||||||
if any(seg in clean_path for seg in ["/.git", "/.system_generated", "/.gemini", "platform_data.db", ".secret_key", ".env", ".jsonl"]):
|
".jsonl", ".env", ".bak", ".sql", ".log", ".conf", ".ini", ".yaml", ".yml"
|
||||||
|
)
|
||||||
|
if any(clean_path.endswith(ext) for ext in blocked_extensions):
|
||||||
return True
|
return True
|
||||||
if "/." in clean_path or clean_path.startswith("."):
|
if "/." in clean_path or clean_path.startswith("."):
|
||||||
return True
|
return True
|
||||||
if clean_path.startswith("/exams/") or clean_path == "/exams":
|
if clean_path.startswith("/exams/") or clean_path == "/exams":
|
||||||
return True
|
return True
|
||||||
if clean_path in ("/server.py", "server.py"):
|
if any(seg in clean_path for seg in ["/.git", "/.system_generated", "/.gemini", "platform_data.db", ".secret_key"]):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# 2. Gefährliche Endungen für Server-Root und PHP/DB
|
|
||||||
if any(clean_path.endswith(ext) for ext in [".db", ".sqlite", ".sqlite3", ".secret_key", ".php", ".env", ".bak", ".conf"]):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# 3. Kursdateien in den Lehrpfaden und Kursordnern explizit erlauben
|
|
||||||
is_course_file = (
|
|
||||||
clean_path.startswith("/lehrpfad_") or
|
|
||||||
clean_path.startswith("lehrpfad_") or
|
|
||||||
clean_path.startswith("/courses/") or
|
|
||||||
clean_path.startswith("courses/")
|
|
||||||
)
|
|
||||||
if not is_course_file:
|
|
||||||
blocked_outside = (".py", ".sh", ".sql", ".log", ".ini", ".yaml", ".yml")
|
|
||||||
if any(clean_path.endswith(ext) for ext in blocked_outside):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
|
|
@ -1074,250 +1056,6 @@ class PlatformRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
conn.close()
|
conn.close()
|
||||||
return self.send_json({"success": True, "message": "Einstellungen gespeichert"})
|
return self.send_json({"success": True, "message": "Einstellungen gespeichert"})
|
||||||
|
|
||||||
# 14. AUTOMATISIERTER RUNNER TESTSUITE ENDPUNKT (BASH, GIT, DNS, POWERSHELL, AD)
|
|
||||||
elif path == "/api/runners/test":
|
|
||||||
user_token = self.get_auth_user()
|
|
||||||
if not user_token:
|
|
||||||
return self.send_json({"error": "Nicht authentifiziert. Bitte melde dich an, um Tests auszuführen."}, 401)
|
|
||||||
|
|
||||||
language = str(body.get("language", "")).strip().lower()
|
|
||||||
base_path = str(body.get("base_path", "")).strip()
|
|
||||||
user_code = str(body.get("user_code", ""))[:100_000]
|
|
||||||
task_file = str(body.get("task_file", "")).strip()
|
|
||||||
test_file = str(body.get("test_file", "")).strip()
|
|
||||||
|
|
||||||
if not base_path or ".." in base_path or base_path.startswith("/") or "\\" in base_path:
|
|
||||||
return self.send_json({"error": "Ungültiger Pfad"}, 400)
|
|
||||||
|
|
||||||
chapter_dir = os.path.normpath(os.path.join(str(BASE_DIR), base_path))
|
|
||||||
if not os.path.isdir(chapter_dir) or not chapter_dir.startswith(str(BASE_DIR)):
|
|
||||||
return self.send_json({"error": "Kapitelverzeichnis nicht gefunden"}, 404)
|
|
||||||
|
|
||||||
# Security: Gefährliche Shell/PowerShell-Befehle blockieren
|
|
||||||
danger_patterns = [
|
|
||||||
r"\brm\s+-[rf]*\s+/(?:\s|$)",
|
|
||||||
r"\bmkfs\b",
|
|
||||||
r"\bdd\s+if=",
|
|
||||||
r"\bshutdown\b",
|
|
||||||
r"\breboot\b",
|
|
||||||
r":\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;",
|
|
||||||
r"\bcurl\b.*\|\s*(?:bash|sh)\b",
|
|
||||||
r"Remove-Item\s+-Recurse\s+[\'\"]?/(?:[\'\"]|\s|$)",
|
|
||||||
r"Format-Volume"
|
|
||||||
]
|
|
||||||
for pat in danger_patterns:
|
|
||||||
if re.search(pat, user_code, re.IGNORECASE):
|
|
||||||
return self.send_json({
|
|
||||||
"success": False,
|
|
||||||
"error": "Sicherheits-Sperre: Unzulässiger Systembefehl im Code erkannt.",
|
|
||||||
"stdout": "❌ Ausführung durch Sicherheitsfilter blockiert.\n",
|
|
||||||
"failures": 1,
|
|
||||||
"total": 1
|
|
||||||
}, 400)
|
|
||||||
|
|
||||||
sandbox = tempfile.mkdtemp(prefix="test_sandbox_")
|
|
||||||
try:
|
|
||||||
# 1. BASH / GIT / DNS EXECUTION
|
|
||||||
if language in ("bash", "shell", "git", "dns_records", "dns"):
|
|
||||||
actual_task_file = task_file or "aufgabe.sh"
|
|
||||||
actual_test_file = test_file or "test_aufgabe.sh"
|
|
||||||
src_test = os.path.join(chapter_dir, actual_test_file)
|
|
||||||
if not os.path.isfile(src_test):
|
|
||||||
return self.send_json({"error": f"Testdatei '{actual_test_file}' nicht gefunden"}, 404)
|
|
||||||
|
|
||||||
shutil.copy(src_test, os.path.join(sandbox, actual_test_file))
|
|
||||||
user_script_path = os.path.join(sandbox, actual_task_file)
|
|
||||||
with open(user_script_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write(user_code)
|
|
||||||
os.chmod(user_script_path, 0o755)
|
|
||||||
os.chmod(os.path.join(sandbox, actual_test_file), 0o755)
|
|
||||||
|
|
||||||
env = os.environ.copy()
|
|
||||||
env["HOME"] = sandbox
|
|
||||||
env["TERM"] = "xterm-256color"
|
|
||||||
|
|
||||||
proc = subprocess.run(
|
|
||||||
["bash", actual_test_file, actual_task_file],
|
|
||||||
cwd=sandbox,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=12,
|
|
||||||
env=env
|
|
||||||
)
|
|
||||||
|
|
||||||
stdout = proc.stdout or ""
|
|
||||||
stderr = proc.stderr or ""
|
|
||||||
exit_code = proc.returncode
|
|
||||||
|
|
||||||
passed_match = re.search(r"(\d+)\s+von\s+(\d+)\s+Tests\s+bestanden", stdout)
|
|
||||||
fail_match = re.search(r"(\d+)\s+von\s+(\d+)\s+Tests\s+fehlgeschlagen", stdout)
|
|
||||||
|
|
||||||
if exit_code == 0:
|
|
||||||
failures = 0
|
|
||||||
passed = int(passed_match.group(1)) if passed_match else 4
|
|
||||||
total = int(passed_match.group(2)) if passed_match else passed
|
|
||||||
else:
|
|
||||||
if fail_match:
|
|
||||||
failures = int(fail_match.group(1))
|
|
||||||
total = int(fail_match.group(2))
|
|
||||||
passed = max(0, total - failures)
|
|
||||||
else:
|
|
||||||
failures = 1
|
|
||||||
passed = 0
|
|
||||||
total = 1
|
|
||||||
|
|
||||||
return self.send_json({
|
|
||||||
"success": exit_code == 0 and failures == 0 and total > 0,
|
|
||||||
"exit_code": exit_code,
|
|
||||||
"stdout": stdout,
|
|
||||||
"stderr": stderr,
|
|
||||||
"total": total,
|
|
||||||
"passed": passed,
|
|
||||||
"failures": failures
|
|
||||||
})
|
|
||||||
|
|
||||||
# 2. POWERSHELL / ACTIVE DIRECTORY EXECUTION
|
|
||||||
elif language in ("powershell", "pwsh", "active_directory", "ad"):
|
|
||||||
actual_task_file = task_file or "aufgabe.ps1"
|
|
||||||
actual_test_file = test_file or "test_aufgabe.ps1"
|
|
||||||
src_test = os.path.join(chapter_dir, actual_test_file)
|
|
||||||
if not os.path.isfile(src_test):
|
|
||||||
return self.send_json({"error": f"Testdatei '{actual_test_file}' nicht gefunden"}, 404)
|
|
||||||
|
|
||||||
shutil.copy(src_test, os.path.join(sandbox, actual_test_file))
|
|
||||||
user_script_path = os.path.join(sandbox, actual_task_file)
|
|
||||||
with open(user_script_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write(user_code)
|
|
||||||
|
|
||||||
pester_shim = f"""
|
|
||||||
function Describe ($name, [ScriptBlock]$block) {{
|
|
||||||
Write-Host "`n🧪 Testsuite: $name" -ForegroundColor Cyan
|
|
||||||
& $block
|
|
||||||
}}
|
|
||||||
function Context ($name, [ScriptBlock]$block) {{
|
|
||||||
Write-Host " 📂 $name" -ForegroundColor DarkCyan
|
|
||||||
& $block
|
|
||||||
}}
|
|
||||||
$script:PesterTotal = 0
|
|
||||||
$script:PesterPassed = 0
|
|
||||||
$script:PesterFailed = 0
|
|
||||||
|
|
||||||
function It ($name, [ScriptBlock]$block) {{
|
|
||||||
$script:PesterTotal++
|
|
||||||
try {{
|
|
||||||
& $block
|
|
||||||
Write-Host " [+] $name [Passed]" -ForegroundColor Green
|
|
||||||
$script:PesterPassed++
|
|
||||||
}} catch {{
|
|
||||||
Write-Host " [-] $name [Failed]" -ForegroundColor Red
|
|
||||||
Write-Host " $($_.Exception.Message)" -ForegroundColor DarkGray
|
|
||||||
$script:PesterFailed++
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
|
|
||||||
function Should {{
|
|
||||||
[CmdletBinding()]
|
|
||||||
param(
|
|
||||||
[Parameter(ValueFromPipeline = $true)]
|
|
||||||
$Actual,
|
|
||||||
[switch]$Be,
|
|
||||||
[switch]$Not,
|
|
||||||
[switch]$BeGreaterThan,
|
|
||||||
[switch]$BeLessThan,
|
|
||||||
[switch]$Match,
|
|
||||||
[Parameter(Position = 0)]
|
|
||||||
$Expected
|
|
||||||
)
|
|
||||||
process {{
|
|
||||||
if ($Be) {{
|
|
||||||
if ("$Actual" -ne "$Expected") {{
|
|
||||||
throw "Expected: $Expected, but got: $Actual"
|
|
||||||
}}
|
|
||||||
}} elseif ($Not) {{
|
|
||||||
if ("$Actual" -eq "$Expected") {{
|
|
||||||
throw "Expected not: $Expected, but got equal value"
|
|
||||||
}}
|
|
||||||
}} elseif ($Match) {{
|
|
||||||
if ("$Actual" -notmatch "$Expected") {{
|
|
||||||
throw "Expected $Actual to match $Expected"
|
|
||||||
}}
|
|
||||||
}} else {{
|
|
||||||
if ("$Actual" -ne "$Expected") {{
|
|
||||||
throw "Expected: $Expected, but got: $Actual"
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
|
|
||||||
. ./{actual_task_file}
|
|
||||||
. ./{actual_test_file}
|
|
||||||
|
|
||||||
Write-Host "`n📊 Gesamtergebnis: $script:PesterPassed von $script:PesterTotal Tests bestanden."
|
|
||||||
if ($script:PesterFailed -gt 0 -or $script:PesterTotal -eq 0) {{
|
|
||||||
Write-Host "❌ $script:PesterFailed Test(s) fehlgeschlagen!" -ForegroundColor Red
|
|
||||||
exit 1
|
|
||||||
}} else {{
|
|
||||||
Write-Host "🎉 Alle $script:PesterTotal Tests erfolgreich bestanden!" -ForegroundColor Green
|
|
||||||
exit 0
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
runner_path = os.path.join(sandbox, "run_pester_test.ps1")
|
|
||||||
with open(runner_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write(pester_shim)
|
|
||||||
|
|
||||||
proc = subprocess.run(
|
|
||||||
["pwsh", "-NoProfile", "-NonInteractive", "-File", "run_pester_test.ps1"],
|
|
||||||
cwd=sandbox,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=12
|
|
||||||
)
|
|
||||||
|
|
||||||
stdout = proc.stdout or ""
|
|
||||||
stderr = proc.stderr or ""
|
|
||||||
exit_code = proc.returncode
|
|
||||||
|
|
||||||
passed_match = re.search(r"(\d+)\s+von\s+(\d+)\s+Tests\s+bestanden", stdout)
|
|
||||||
if passed_match:
|
|
||||||
passed = int(passed_match.group(1))
|
|
||||||
total = int(passed_match.group(2))
|
|
||||||
failures = total - passed
|
|
||||||
else:
|
|
||||||
if exit_code == 0:
|
|
||||||
passed = 4
|
|
||||||
total = 4
|
|
||||||
failures = 0
|
|
||||||
else:
|
|
||||||
passed = 0
|
|
||||||
total = 1
|
|
||||||
failures = 1
|
|
||||||
|
|
||||||
return self.send_json({
|
|
||||||
"success": exit_code == 0 and failures == 0 and total > 0,
|
|
||||||
"exit_code": exit_code,
|
|
||||||
"stdout": stdout,
|
|
||||||
"stderr": stderr,
|
|
||||||
"total": total,
|
|
||||||
"passed": passed,
|
|
||||||
"failures": failures
|
|
||||||
})
|
|
||||||
|
|
||||||
else:
|
|
||||||
return self.send_json({"error": f"Kein Server-Runner für Sprache '{language}' konfiguriert"}, 400)
|
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
return self.send_json({
|
|
||||||
"success": False,
|
|
||||||
"error": "Zeitüberschreitung: Testausführung dauerte länger als 12 Sekunden.",
|
|
||||||
"stdout": "⏱️ Zeitüberschreitung bei der Testausführung.\n",
|
|
||||||
"failures": 1,
|
|
||||||
"total": 1
|
|
||||||
}, 408)
|
|
||||||
except Exception as e:
|
|
||||||
return self.send_json({"error": f"Serverfehler bei Testausführung: {str(e)}"}, 500)
|
|
||||||
finally:
|
|
||||||
shutil.rmtree(sandbox, ignore_errors=True)
|
|
||||||
|
|
||||||
return self.send_json({"error": "Endpunkt nicht gefunden"}, 404)
|
return self.send_json({"error": "Endpunkt nicht gefunden"}, 404)
|
||||||
|
|
||||||
def run_server():
|
def run_server():
|
||||||
|
|
|
||||||
|
|
@ -357,20 +357,20 @@
|
||||||
<script src="assets/app_auth.js?v=2.2.1"></script>
|
<script src="assets/app_auth.js?v=2.2.1"></script>
|
||||||
<script src="assets/parsons.js"></script>
|
<script src="assets/parsons.js"></script>
|
||||||
<script src="assets/code_guard.js"></script>
|
<script src="assets/code_guard.js"></script>
|
||||||
<script src="assets/runners/runner_registry.js?v=2.2.2"></script>
|
<script src="assets/runners/runner_registry.js"></script>
|
||||||
<script src="assets/runners/python_runner.js?v=2.2.2"></script>
|
<script src="assets/runners/python_runner.js"></script>
|
||||||
<script src="assets/runners/js_runner.js?v=2.2.2"></script>
|
<script src="assets/runners/js_runner.js"></script>
|
||||||
<script src="assets/runners/sql_runner.js?v=2.2.2"></script>
|
<script src="assets/runners/sql_runner.js"></script>
|
||||||
<script src="assets/runners/shell_runner.js?v=2.2.2"></script>
|
<script src="assets/runners/shell_runner.js"></script>
|
||||||
<script src="assets/runners/powershell_runner.js?v=2.2.2"></script>
|
<script src="assets/runners/powershell_runner.js"></script>
|
||||||
<script src="assets/runners/html_css_runner.js?v=2.2.2"></script>
|
<script src="assets/runners/html_css_runner.js"></script>
|
||||||
<script src="assets/runners/go_runner.js?v=2.2.2"></script>
|
<script src="assets/runners/go_runner.js"></script>
|
||||||
<script src="assets/runners/java_runner.js?v=2.2.2"></script>
|
<script src="assets/runners/java_runner.js"></script>
|
||||||
<script src="assets/runners/rust_runner.js?v=2.2.2"></script>
|
<script src="assets/runners/rust_runner.js"></script>
|
||||||
<script src="assets/runners/csharp_runner.js?v=2.2.2"></script>
|
<script src="assets/runners/csharp_runner.js"></script>
|
||||||
<script src="assets/runners/cpp_runner.js?v=2.2.2"></script>
|
<script src="assets/runners/cpp_runner.js"></script>
|
||||||
<script src="assets/courses_manifest.js?v=2.2.2"></script>
|
<script src="assets/courses_manifest.js"></script>
|
||||||
<script src="assets/monaco_ide.js?v=2.2.2"></script>
|
<script src="assets/monaco_ide.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
|
@ -587,11 +587,6 @@
|
||||||
const lang = currentCourseManifest ? (currentCourseManifest.language || "python") : "python";
|
const lang = currentCourseManifest ? (currentCourseManifest.language || "python") : "python";
|
||||||
window.initWorkspace({
|
window.initWorkspace({
|
||||||
chapterId: chapterId,
|
chapterId: chapterId,
|
||||||
basePath: basePath,
|
|
||||||
taskFile: taskFile,
|
|
||||||
testFile: testFile,
|
|
||||||
courseId: courseId,
|
|
||||||
chapterFolder: chapterFolder,
|
|
||||||
starterCode: aufgabeCode,
|
starterCode: aufgabeCode,
|
||||||
testCode: testCode,
|
testCode: testCode,
|
||||||
solutionCode: loesungCode
|
solutionCode: loesungCode
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue