cambios profundos con grok
This commit is contained in:
+59
-4
@@ -38,8 +38,17 @@ app.post('/api/testcases', (req, res) => {
|
||||
// Primary: persist to YAML tree
|
||||
saveTestcases(testcases);
|
||||
|
||||
// Transition: still write the legacy JSON so other tools don't break yet
|
||||
fs.writeFileSync(DATA_FILE, JSON.stringify(testcases, null, 2), 'utf8');
|
||||
// Transition: still write the legacy JSON so other tools don't break yet (best effort)
|
||||
try {
|
||||
const legacyDir = path.dirname(DATA_FILE);
|
||||
if (!fs.existsSync(legacyDir)) {
|
||||
fs.mkdirSync(legacyDir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(DATA_FILE, JSON.stringify(testcases, null, 2), 'utf8');
|
||||
} catch (legacyErr) {
|
||||
console.warn('Warning: Could not write legacy JSON file:', legacyErr.message);
|
||||
// Continue anyway - YAML is the source of truth now
|
||||
}
|
||||
|
||||
res.json({ success: true, count: testcases.length, source: 'yaml+json' });
|
||||
} catch (e) {
|
||||
@@ -81,7 +90,7 @@ app.get('/api/export/report', (req, res) => {
|
||||
// Simple summary HTML (lightweight)
|
||||
app.get('/api/export/summary', (req, res) => {
|
||||
try {
|
||||
const tests = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
|
||||
const tests = loadTestcases();
|
||||
const total = tests.length;
|
||||
const automated = tests.filter(t => t.status === 'automated').length;
|
||||
const autoPct = ((automated / total) * 100).toFixed(1);
|
||||
@@ -113,6 +122,29 @@ const PLANS_DIR = path.join(__dirname, '..', 'output', 'testplans', 'plans');
|
||||
// Ensure plans dir
|
||||
if (!fs.existsSync(PLANS_DIR)) fs.mkdirSync(PLANS_DIR, { recursive: true });
|
||||
|
||||
function computeAutomationSummary(tests) {
|
||||
const total = tests.length;
|
||||
let automated = 0, partial = 0, manual = 0;
|
||||
|
||||
tests.forEach(t => {
|
||||
const status = t.automation_status || t.status || 'manual';
|
||||
if (status === 'automated') automated++;
|
||||
else if (status === 'partial') partial++;
|
||||
else manual++;
|
||||
});
|
||||
|
||||
const automatable = automated + partial;
|
||||
const automatable_percentage = total > 0 ? Math.round((automatable / total) * 100 * 10) / 10 : 0;
|
||||
|
||||
return {
|
||||
total,
|
||||
automated,
|
||||
partial,
|
||||
manual,
|
||||
automatable_percentage
|
||||
};
|
||||
}
|
||||
|
||||
function getPlanPath(planId) {
|
||||
return path.join(PLANS_DIR, `${planId}.json`);
|
||||
}
|
||||
@@ -126,7 +158,8 @@ app.post('/api/test-plans', (req, res) => {
|
||||
return res.status(400).json({ error: 'No test IDs provided' });
|
||||
}
|
||||
|
||||
const allTests = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
|
||||
// Use the proper loader instead of the broken legacy JSON file
|
||||
const allTests = loadTestcases();
|
||||
const selectedTests = allTests.filter(t => ids.includes(t.id));
|
||||
|
||||
if (selectedTests.length === 0) {
|
||||
@@ -151,6 +184,8 @@ app.post('/api/test-plans', (req, res) => {
|
||||
}))
|
||||
};
|
||||
|
||||
plan.automation_summary = computeAutomationSummary(plan.tests);
|
||||
|
||||
fs.writeFileSync(getPlanPath(planId), JSON.stringify(plan, null, 2));
|
||||
|
||||
res.json({ success: true, planId, name: plan.name });
|
||||
@@ -166,6 +201,9 @@ app.get('/api/test-plans/:id', (req, res) => {
|
||||
if (!fs.existsSync(planPath)) return res.status(404).json({ error: 'Plan not found' });
|
||||
|
||||
const plan = JSON.parse(fs.readFileSync(planPath, 'utf8'));
|
||||
if (!plan.automation_summary && plan.tests) {
|
||||
plan.automation_summary = computeAutomationSummary(plan.tests);
|
||||
}
|
||||
res.json(plan);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: 'Error loading plan' });
|
||||
@@ -185,6 +223,8 @@ app.post('/api/test-plans/:id', (req, res) => {
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
updated.automation_summary = computeAutomationSummary(updated.tests || []);
|
||||
|
||||
fs.writeFileSync(planPath, JSON.stringify(updated, null, 2));
|
||||
res.json({ success: true });
|
||||
} catch (e) {
|
||||
@@ -192,6 +232,21 @@ app.post('/api/test-plans/:id', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Delete a plan
|
||||
app.delete('/api/test-plans/:id', (req, res) => {
|
||||
try {
|
||||
const planPath = getPlanPath(req.params.id);
|
||||
if (!fs.existsSync(planPath)) {
|
||||
return res.status(404).json({ error: 'Plan not found' });
|
||||
}
|
||||
|
||||
fs.unlinkSync(planPath);
|
||||
res.json({ success: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: 'Failed to delete plan' });
|
||||
}
|
||||
});
|
||||
|
||||
// List all plans (for future tester portal)
|
||||
app.get('/api/test-plans', (req, res) => {
|
||||
try {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* save-testcases.js
|
||||
*
|
||||
* Writes an array of legacy-shaped testcases back to the individual YAML files.
|
||||
* Tries to preserve existing per-step automation flags and custom data.
|
||||
* Steps are kept simple (action + expected + optional notes). Automation decision lives at testcase level.
|
||||
*
|
||||
* This is part of the transition so that the editor can save directly to YAML.
|
||||
*/
|
||||
@@ -14,52 +14,43 @@ const yaml = require('js-yaml');
|
||||
const TESTCASES_ROOT = path.join(__dirname, '..', '..', 'testcases');
|
||||
|
||||
function getYamlPath(legacyTest) {
|
||||
const project = 'pbx'; // transition: all current data lives under pbx project
|
||||
const module = legacyTest.module || 'unknown';
|
||||
const top = legacyTest.module || 'unknown'; // core or pbx (top level per new structure)
|
||||
const rawGroup = legacyTest.group || 'unknown';
|
||||
const group = rawGroup.replace(/^core-|^pbx-/, '');
|
||||
return path.join(TESTCASES_ROOT, project, module, group, `${legacyTest.id}.yaml`);
|
||||
return path.join(TESTCASES_ROOT, top, group, `${legacyTest.id}.yaml`);
|
||||
}
|
||||
|
||||
function buildCanonicalDoc(legacy, existing = {}) {
|
||||
const cf = legacy.custom_fields || existing.custom_fields || {};
|
||||
|
||||
// Support rich steps from new per-step editor (Fase B+)
|
||||
// Steps are now simple: action + expected + optional notes (no per-step automation flags)
|
||||
let steps = [];
|
||||
const incomingSteps = legacy.steps || [];
|
||||
|
||||
if (incomingSteps.length > 0 && typeof incomingSteps[0] === 'object' && incomingSteps[0] !== null) {
|
||||
// Rich steps sent from frontend
|
||||
steps = incomingSteps.map(s => ({
|
||||
action: s.action || '',
|
||||
expected: s.expected || '',
|
||||
automatable: s.automatable !== undefined ? !!s.automatable : (legacy.status !== 'manual'),
|
||||
...(s.reason_not_automatable ? { reason_not_automatable: s.reason_not_automatable } : {})
|
||||
}));
|
||||
steps = incomingSteps.map(s => {
|
||||
const step = {
|
||||
action: s.action || '',
|
||||
expected: s.expected || '',
|
||||
};
|
||||
if (s.notes) step.notes = s.notes;
|
||||
return step;
|
||||
});
|
||||
} else {
|
||||
// Legacy flat strings
|
||||
steps = incomingSteps.map((action, i) => {
|
||||
const prev = (existing.steps && existing.steps[i]) || {};
|
||||
return {
|
||||
const step = {
|
||||
action: action,
|
||||
expected: prev.expected || '',
|
||||
automatable: prev.automatable !== undefined ? prev.automatable : (legacy.status !== 'manual'),
|
||||
...(prev.reason_not_automatable ? { reason_not_automatable: prev.reason_not_automatable } : {})
|
||||
};
|
||||
if (prev.notes) step.notes = prev.notes;
|
||||
return step;
|
||||
});
|
||||
}
|
||||
|
||||
// If existing had more detailed steps, prefer their automatable flags
|
||||
if (existing.steps && existing.steps.length === steps.length) {
|
||||
steps = existing.steps.map((prev, i) => ({
|
||||
...prev,
|
||||
action: steps[i].action
|
||||
}));
|
||||
}
|
||||
|
||||
const doc = {
|
||||
id: legacy.id,
|
||||
project: 'pbx',
|
||||
module: legacy.module,
|
||||
group: legacy.group,
|
||||
title: legacy.description || legacy.title,
|
||||
@@ -72,6 +63,7 @@ function buildCanonicalDoc(legacy, existing = {}) {
|
||||
postconditions: existing.postconditions || [],
|
||||
steps,
|
||||
automation_status: legacy.status,
|
||||
reason_not_automatable: legacy.reason_not_automatable || existing.reason_not_automatable || undefined,
|
||||
file: legacy.file || existing.file || null,
|
||||
custom_fields: {
|
||||
...cf,
|
||||
@@ -79,10 +71,14 @@ function buildCanonicalDoc(legacy, existing = {}) {
|
||||
}
|
||||
};
|
||||
|
||||
// Carry over extra legacy fields the UI might send
|
||||
// Carry over legacy fields the UI might still send
|
||||
if (legacy.manualReason) doc.custom_fields.manualReason = legacy.manualReason;
|
||||
if (legacy.partialNotes) doc.custom_fields.partialNotes = legacy.partialNotes;
|
||||
|
||||
// Keep high-level summaries if present
|
||||
if (legacy.automatable) doc.automatable = legacy.automatable;
|
||||
if (legacy.notYet) doc.notYet = legacy.notYet;
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user