Files
schoolhub/workflow/WorkflowTest.gs
T
2026-04-24 23:45:04 +00:00

762 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 🧪 WORKFLOW INTEGRATION TESTS
* =============================================================================
* This file contains manual test helpers to verify the connection between
* SchoolHub and external workflow tools (Vikunja & Gitea).
*
* INSTRUCTIONS:
* 1. Select the desired test function from the toolbar dropdown.
* 2. Click "Run".
* 3. Check the "Execution Log" for the results.
* =============================================================================
*/
/**
* Test 1: Verify Script Properties are readable.
* Goal: Ensure VIKUNJA_BASE_URL, VIKUNJA_TOKEN, etc., are set.
*/
function testWorkflowConfig() {
console.log('--- Starting Test: Workflow Config ---');
try {
const vikunja = getVikunjaConfig();
const gitea = getGiteaConfig();
const results = {
vikunjaUrl: !!vikunja.baseUrl,
vikunjaToken: !!vikunja.token,
giteaUrl: !!gitea.baseUrl,
giteaToken: !!gitea.token,
giteaOwner: !!gitea.owner,
giteaRepo: !!gitea.repo
};
const allPassed = Object.values(results).every(val => val === true);
if (allPassed) {
console.log('✅ PASS: All required Script Properties are present.');
} else {
console.error('❌ FAIL: Some properties are missing:');
console.log(JSON.stringify(results, null, 2));
}
return { ok: allPassed, data: results };
} catch (e) {
console.error('❌ ERROR: ' + e.message);
return { ok: false, message: e.message };
}
}
/**
* Test 2: Verify Gitea Connectivity.
* Goal: Perform a read-only request to the repository root.
*/
function testGiteaConnection() {
console.log('--- Starting Test: Gitea Connection ---');
const res = getGiteaRepo();
if (res.ok) {
console.log('✅ PASS: Gitea authenticated successfully.');
console.log('Repo Name: ' + res.data.name);
return { ok: true, data: res.data };
} else {
console.error('❌ FAIL: Gitea connection failed: ' + res.message);
return { ok: false, message: res.message };
}
}
/**
* Test 3: Verify Vikunja Connectivity using a Task ID.
* @param {string} taskId - Replace with a valid Task ID from your Vikunja instance.
*/
function testVikunjaRead() {
const taskId = 'REPLACE_WITH_ACTUAL_TASK_ID';
if (taskId === 'REPLACE_WITH_ACTUAL_TASK_ID') {
console.warn('⚠️ SKIP: Please provide a valid taskId in the code first.');
return { ok: false, message: 'No Task ID provided' };
}
console.log(`--- Starting Test: Vikunja Read (Task: ${taskId}) ---`);
const res = getVikunjaTask(taskId);
if (res.ok) {
console.log('✅ PASS: Vikunja authenticated and task retrieved.');
console.log('Task Title: ' + (res.data.title || 'No Title'));
return { ok: true, data: res.data };
} else {
console.error('❌ FAIL: Vikunja read failed: ' + res.message);
if (res.rawBody) console.log('Raw Response: ' + res.rawBody);
return { ok: false, message: res.message };
}
}
/**
* Test 4: Verify Vikunja Write Operation.
* Goal: Add a clearly marked test comment.
* @param {string} taskId - Replace with a valid Task ID.
*/
function testVikunjaComment() {
const taskId = 'REPLACE_WITH_ACTUAL_TASK_ID';
if (taskId === 'REPLACE_WITH_ACTUAL_TASK_ID') {
console.warn('⚠️ SKIP: Please provide a valid taskId in the code first.');
return { ok: false, message: 'No Task ID provided' };
}
console.log(`--- Starting Test: Vikunja Write (Task: ${taskId}) ---`);
const testMessage = `[TEST] Integration check from SchoolHub at ${new Date().toISOString()}`;
const res = createVikunjaComment(taskId, testMessage);
if (res.ok) {
console.log('✅ PASS: Test comment successfully created.');
return { ok: true };
} else {
console.error('❌ FAIL: Vikunja write failed: ' + res.message);
return { ok: false, message: res.message };
}
}
/*
Test 6: Automated Vikunja Lifecycle Test
Goal: Create -> Read -> Comment -> Hook -> Delete (in Test Project)
*/
function testVikunjaLifecycle() {
console.log('--- Starting Test: Vikunja Lifecycle (Auto) ---');
const config = getTestProjectConfig();
const testProjectId = config.testProjectId;
if (!testProjectId) {
console.warn('⚠️ SKIP: VIKUNJA_TEST_PROJECT_ID not found in Script Properties.');
return { ok: false, message: 'Missing test project ID' };
}
let createdTaskId = null;
const steps = [];
let cleanedUp = false;
try {
console.log('Step 1: Creating temporary task...');
const createData = {
title: `[TEST][AUTO][SchoolHub] Lifecycle Test ${new Date().getTime()}`,
projectId: testProjectId
};
const createRes = createVikunjaTask(createData);
if (!createRes.ok || !createRes.data || !createRes.data.id) {
throw new Error(`Task Creation Failed: ${createRes.message || 'No ID returned'}. Raw: ${createRes.rawBody || 'N/A'}`);
}
createdTaskId = createRes.data.id;
steps.push('TASK_CREATED');
console.log(`✅ Task created with ID: ${createdTaskId}`);
console.log('Step 2: Reading task back...');
const readRes = getVikunjaTask(createdTaskId);
if (!readRes.ok) throw new Error(`Read failed: ${readRes.message}`);
steps.push('TASK_READ');
console.log(`✅ Task verified: ${readRes.data.title}`);
console.log('Step 3: Adding test comment...');
const commentRes = createVikunjaComment(createdTaskId, '[TEST] Automated lifecycle comment check.');
if (!commentRes.ok) {
throw new Error(`Comment failed (Code: ${commentRes.code}): ${commentRes.message}. Raw: ${commentRes.rawBody}`);
}
steps.push('COMMENT_ADDED');
console.log('✅ Comment added successfully');
console.log('Step 4: Running workflow hook...');
const hookRes = workflowStartFeature('TEST-AUTO', createdTaskId);
if (!hookRes.ok) throw new Error(`Hook failed: ${hookRes.message}`);
steps.push('HOOK_EXECUTED');
console.log('✅ Workflow hook executed successfully');
return {
ok: true,
createdTaskId,
cleanedUp: cleanedUp,
steps,
message: 'Full lifecycle completed successfully'
};
} catch (e) {
console.error(`❌ Lifecycle Test Failed at step ${steps.length + 1}: ${e.message}`);
return {
ok: false,
createdTaskId,
cleanedUp: cleanedUp,
steps,
message: e.message
};
} finally {
if (createdTaskId) {
console.log('Final Step: Cleaning up temporary task...');
const delRes = deleteVikunjaTask(createdTaskId);
if (delRes.ok) {
console.log('✅ Temporary task deleted.');
cleanedUp = true;
} else {
console.error(`❌ Cleanup failed for task ${createdTaskId}: ${delRes.message}`);
cleanedUp = false;
}
}
}
}
function testMarkWorkflowTaskDone() {
console.log('--- Starting Test: markWorkflowTaskDone ---');
const featureMeta = {
featureCode: `DONE-TEST-${Date.now()}`,
title: 'Automated Done Status Test',
type: 'feature',
module: 'general'
};
try {
// 1. Ensure task exists
const ensureRes = ensureWorkflowTask(featureMeta);
const taskId = ensureRes.taskId;
console.log(`Step 1: Task created/resolved: ${taskId}`);
// 2. Mark as done
markWorkflowTaskDone_(featureMeta, true);
console.log('Step 2: markWorkflowTaskDone_ executed');
// 3. Verify status
const taskRes = getVikunjaTask(taskId);
if (!taskRes.ok) throw new Error(`Read failed: ${taskRes.message}`);
const task = taskRes.data;
assertEqual_('Task is marked done', task.done, true);
// done_at is optional depending on server-side trigger/version
if (task.done_at) {
assertTrue_('Task has done_at timestamp', !!task.done_at);
} else {
console.log('️ Note: Server did not return done_at, but task.done is true.');
}
console.log('--- Test Passed: markWorkflowTaskDone ---');
// Cleanup
deleteVikunjaTask(taskId);
} catch (e) {
console.error(`❌ Test Failed: ${e.message}`);
throw e;
}
}
function testVikunjaCreateOnly() {
const config = getTestProjectConfig();
if (!config.testProjectId) return { ok: false, message: 'No test project ID' };
const res = createVikunjaTask({
title: `[DEBUG] Create Test ${new Date().getTime()}`,
projectId: config.testProjectId,
priority: 4
});
console.log(res.ok ? `✅ Created: ${res.data && res.data.id}` : `❌ Failed: ${res.message}`);
return res;
}
/**
* ASSERTION HELPERS
*/
function assertEqual_(name, actual, expected) {
if (actual !== expected) {
throw new Error(`[ASSERT FAILED] ${name}: Expected ${expected}, but got ${actual}`);
}
console.log(`${name}: ${actual} == ${expected}`);
}
function assertArrayEqual_(name, actual, expected) {
const a = JSON.stringify(actual);
const e = JSON.stringify(expected);
if (a !== e) {
throw new Error(`[ASSERT FAILED] ${name}: Expected ${e}, but got ${a}`);
}
console.log(`${name}: ${a} == ${e}`);
}
function assertTrue_(name, condition) {
if (!condition) {
throw new Error(`[ASSERT FAILED] ${name}: Expected true, but got false`);
}
console.log(`${name}: true`);
}
/**
* Test: Workflow Priority Rules
* Goal: Validate that the priority mapping (5..1) follows business rules.
*/
function testWorkflowPriorityRules() {
console.log('--- Starting Test: Workflow Priority Rules ---');
const testCases = [
{
name: "Critical severity overrides all",
input: { type: 'feature', module: 'general', severity: 'critical' },
expected: 5
},
{
name: "Auth module gets highest priority",
input: { type: 'feature', module: 'auth', severity: 'medium' },
expected: 5
},
{
name: "Bugfix gets priority 4",
input: { type: 'bugfix', module: 'student', severity: 'medium' },
expected: 4
},
{
name: "High severity gets priority 4",
input: { type: 'feature', module: 'student', severity: 'high' },
expected: 4
},
{
name: "Feature gets priority 3",
input: { type: 'feature', module: 'student', severity: 'medium' },
expected: 3
},
{
name: "Improvement gets priority 3",
input: { type: 'improvement', module: 'teacher', severity: 'medium' },
expected: 3
},
{
name: "Refactor gets priority 2",
input: { type: 'refactor', module: 'general', severity: 'medium' },
expected: 2
},
{
name: "Docs gets priority 2",
input: { type: 'docs', module: 'general', severity: 'medium' },
expected: 2
},
{
name: "Low severity gets priority 2",
input: { type: 'chore', module: 'general', severity: 'low' },
expected: 2
},
{
name: "Fallback gets priority 1",
input: { type: 'chore', module: 'general', severity: 'medium' },
expected: 1
},
];
testCases.forEach(tc => {
const actual = deriveWorkflowPriority_(tc.input);
assertEqual_(tc.name, actual, tc.expected);
});
console.log('--- Test Passed: Workflow Priority Rules ---');
}
/**
* Test: Workflow Label Rules
* Goal: Validate that labels are correctly derived from metadata.
*/
function testWorkflowLabelRules() {
console.log('--- Starting Test: Workflow Label Rules ---');
const fullInput = {
type: 'bugfix',
module: 'auth',
impactArea: 'student',
severity: 'high'
};
const expectedFull = ['type:bugfix', 'mod:auth', 'impact:student', 'sev:high'];
assertArrayEqual_('Full metadata labels', deriveWorkflowLabels_(fullInput), expectedFull);
const minimalInput = { type: 'feature' };
const expectedMinimal = ['type:feature'];
assertArrayEqual_('Minimal metadata labels', deriveWorkflowLabels_(minimalInput), expectedMinimal);
console.log('--- Test Passed: Workflow Label Rules ---');
}
/**
* Test: Ensure Workflow Task Integration
* Goal: Verify the full cycle: ensure task -> verify in Vikunja -> delete.
*/
function testEnsureWorkflowTask() {
console.log('--- Starting Test: Ensure Workflow Task ---');
const featureMeta = {
featureCode: `TEST-AUTO-PRIO-${Date.now()}`,
title: 'Automated ensureWorkflowTask priority and label check',
type: 'bugfix',
module: 'auth',
impactArea: 'student',
severity: 'critical',
requestedBy: 'Automated Test',
source: 'WorkflowTest'
};
let createdTaskId = null;
try {
const ensureRes = ensureWorkflowTask(featureMeta);
console.log('Ensure Result: ' + JSON.stringify(ensureRes));
assertTrue_('Ensure task ok', ensureRes.ok);
assertTrue_('Task ID exists', !!ensureRes.taskId);
assertTrue_('Task state is valid', ['CREATED', 'EXISTING'].includes(ensureRes.state));
createdTaskId = ensureRes.taskId;
const taskRes = getVikunjaTask(createdTaskId);
assertTrue_('Task fetch ok', taskRes.ok);
assertTrue_('Task data exists', !!taskRes.data);
const task = taskRes.data;
assertTrue_('Title contains feature code', task.title.includes(`[${featureMeta.featureCode}]`));
assertEqual_('Task priority matches rule', task.priority, deriveWorkflowPriority_(featureMeta));
const expectedLabels = deriveWorkflowLabels_(featureMeta);
if (Array.isArray(task.labels)) {
const actualLabels = task.labels.map(l => l.title || l.name);
expectedLabels.forEach(el => {
assertTrue_(`Label ${el} present`, actualLabels.includes(el));
});
} else {
console.log('️ Labels not returned as array; verification skipped for this instance.');
}
console.log('--- Test Passed: Ensure Workflow Task ---');
} catch (e) {
console.error(`❌ Test Failed: ${e.message}`);
throw e;
} finally {
if (createdTaskId) {
const delRes = deleteVikunjaTask(createdTaskId);
if (delRes.ok) console.log('✅ Cleanup: Temporary task deleted.');
else console.warn('⚠️ Cleanup: Failed to delete temporary task.');
}
}
}
/**
* Test 5: Workflow Hook Dry Run.
* Goal: Verify the orchestrator can call the client.
* @param {string} taskId - Replace with a valid Task ID.
*/
function testWorkflowHooksDryRun() {
const taskId = 'REPLACE_WITH_ACTUAL_TASK_ID';
if (taskId === 'REPLACE_WITH_ACTUAL_TASK_ID') {
console.warn('⚠️ SKIP: Please provide a valid taskId in the code first.');
return { ok: false, message: 'No Task ID provided' };
}
console.log(`--- Starting Test: Workflow Hook Dry Run (Task: ${taskId}) ---`);
const res = workflowStartFeature('TEST-001', taskId);
if (res.ok) {
console.log('✅ PASS: workflowStartFeature executed successfully.');
return { ok: true };
} else {
console.error(`❌ FAIL: Workflow hook failed: ${res.message}`);
return { ok: false, message: res.message };
}
}
/*
* 🧪 GITEA SYNC INTEGRATION TESTS
* =============================================================================
*/
function testGiteaPathMapping() {
console.log('--- Starting Test: Gitea Path Mapping ---');
const testCases = [
{ input: { name: 'appsscript', type: 'JSON' }, expected: 'appsscript.json' },
{ input: { name: 'Code', type: 'SERVER_JS' }, expected: 'app/Code.gs' },
{ input: { name: 'Index', type: 'HTML' }, expected: 'app/Index.html' },
{ input: { name: 'styles', type: 'HTML' }, expected: 'app/styles.html' },
{ input: { name: 'Readme', type: 'SERVER_JS' }, expected: 'docs/Readme.gs' },
{ input: { name: 'WorkflowConfig', type: 'SERVER_JS' }, expected: 'workflow/WorkflowConfig.gs' },
{ input: { name: 'Unknown', type: 'OTHER' }, expected: null }
];
testCases.forEach(function(tc) {
const actual = mapAppsScriptFileToRepoPath(tc.input);
assertEqual_(
`Path mapping for ${tc.input.name} (${tc.input.type})`,
actual,
tc.expected
);
});
console.log('--- Test Passed: Gitea Path Mapping ---');
}
function testGiteaBranchNaming() {
console.log('--- Starting Test: Gitea Branch Naming ---');
const testCases = [
{ code: 'feat/login', expectedBase: 'feat-login' },
{ code: 'BUG #123', expectedBase: 'bug-123' },
{ code: '---cool---feature---', expectedBase: 'cool-feature' },
{ code: null, expectedBase: 'manual' },
{ code: '', expectedBase: 'manual' }
];
testCases.forEach(function(tc) {
const branch = buildSyncBranchName(tc.code);
assertTrue_(
`Branch starts with sync/ for input: ${tc.code}`,
branch.indexOf('sync/') === 0
);
assertTrue_(
`Branch contains sanitized base "${tc.expectedBase}"`,
branch.indexOf(tc.expectedBase) !== -1
);
assertTrue_(
`Branch ends with timestamp pattern for input: ${tc.code}`,
/\d{8}-\d{4}$/.test(branch)
);
assertTrue_(
`Branch has no duplicate hyphens for input: ${tc.code}`,
!/--/.test(branch)
);
});
console.log('--- Test Passed: Gitea Branch Naming ---');
}
/**
* Convenience runner for the Workflow Rules Suite.
* Runs priority, label, and task assurance tests.
*/
function testWorkflowRulesSuite() {
console.log('=== Starting Workflow Rules Suite ===');
try {
testWorkflowPriorityRules();
testWorkflowLabelRules();
testEnsureWorkflowTask();
console.log('=== Workflow Rules Suite Passed ===');
} catch (e) {
console.error('=== Workflow Rules Suite FAILED ===');
console.error(e.message);
throw e;
}
}
function testPrepareGitSyncPayload() {
console.log('--- Starting Test: Prepare Git Sync Payload ---');
const mockFiles = [
{ name: 'appsscript', type: 'JSON', source: '{ "timeZone": "UTC" }' },
{ name: 'Code', type: 'SERVER_JS', source: 'function test() {}' },
{ name: 'Index', type: 'HTML', source: '<html></html>' },
{ name: 'InvalidFile', type: 'UNKNOWN', source: 'junk' },
{ name: 'MissingSource', type: 'SERVER_JS' },
null
];
const payload = prepareGitSyncPayload('test-feature', mockFiles);
assertTrue_('Base branch is set', !!payload.baseBranch);
assertTrue_('Sync branch starts with sync/', payload.syncBranch.indexOf('sync/') === 0);
assertTrue_('Sync branch contains feature code', payload.syncBranch.indexOf('test-feature') !== -1);
assertEqual_('Valid files mapped correctly', payload.files.length, 4);
assertEqual_('JSON mapped to appsscript.json', payload.files[0].repoPath, 'appsscript.json');
assertEqual_('SERVER_JS mapped to app/Code.gs', payload.files[1].repoPath, 'app/Code.gs');
assertEqual_('HTML mapped to app/Index.html', payload.files[2].repoPath, 'app/Index.html');
assertEqual_('SERVER_JS missing source mapped to workflow/MissingSource.gs', payload.files[3].repoPath, 'workflow/MissingSource.gs');
console.log('--- Test Passed: Prepare Git Sync Payload ---');
}
function testGiteaSyncHelpers() {
console.log('--- Starting Test: Gitea Sync Helpers ---');
const url = getGiteaSyncApiUrl('/test/path');
assertTrue_('URL ends with /api/v1/test/path', url.indexOf('/api/v1/test/path') !== -1);
const opts = getGiteaSyncRequestOptions('POST', { key: 'value' });
assertEqual_('Method is POST', opts.method, 'POST');
assertTrue_('Has Authorization header', !!opts.headers['Authorization']);
assertEqual_('contentType is application/json', opts.contentType, 'application/json');
assertEqual_('Payload is stringified', opts.payload, '{"key":"value"}');
console.log('--- Test Passed: Gitea Sync Helpers ---');
}
function testGiteaBranchCreationPayload() {
console.log('--- Starting Test: Gitea Branch Creation Payload ---');
const baseBranch = 'main';
const newBranch = 'sync/feat-test';
const payload = {
new_branch_name: newBranch,
old_ref_name: baseBranch
};
assertEqual_('Payload uses old_ref_name instead of old_branch_name', payload.old_ref_name, 'main');
assertTrue_('old_branch_name is undefined', payload.old_branch_name === undefined);
console.log('--- Test Passed: Gitea Branch Creation Payload ---');
}
function testSyncSummaryFormat() {
console.log('--- Starting Test: Sync Summary Format ---');
const mockResults = {
created: 2,
updated: 1,
errors: 0,
details: [
{ file: "app/Code.gs", ok: true, message: "" },
{ file: "appsscript.json", ok: true, message: "" },
{ file: "sync-manifest.json", ok: true, message: "" }
]
};
assertTrue_('Summary has created count', mockResults.created === 2);
assertTrue_('Summary has updated count', mockResults.updated === 1);
assertTrue_('Summary has errors count', mockResults.errors === 0);
assertEqual_('Summary details length is correct', mockResults.details.length, 3);
const mockFinalResult = {
ok: true,
branch: 'sync/manual',
summary: mockResults,
pullRequest: { url: 'https://gitea.example.com/pr/1', existed: true }
};
assertTrue_('Final result has branch info', !!mockFinalResult.branch);
assertTrue_('Final result has pullRequest info', !!mockFinalResult.pullRequest);
assertTrue_('Final result pullRequest has url', !!mockFinalResult.pullRequest.url);
assertEqual_('Final result pullRequest existed flag is true', mockFinalResult.pullRequest.existed, true);
console.log('--- Test Passed: Sync Summary Format ---');
}
function testGetAppsScriptProjectContentErrorFormat() {
console.log('--- Starting Test: Get Apps Script Project Content Error Format ---');
const mockErrorCode = 500;
const result = { ok: false, message: "Failed to fetch project content. Code: " + mockErrorCode };
assertEqual_('Result ok is false', result.ok, false);
assertEqual_('Message formatting correct', result.message, "Failed to fetch project content. Code: 500");
const result403 = {
ok: false,
message: "HTTP 403 Forbidden. Ensure Google Apps Script API is enabled in user settings (https://script.google.com/home/usersettings) and OAuth scopes are correctly configured.",
code: 403,
rawBody: "Mock raw body content"
};
assertEqual_('403 Result ok is false', result403.ok, false);
assertEqual_('403 Result code is 403', result403.code, 403);
assertTrue_('Message contains HTTP 403 Forbidden', result403.message.indexOf("HTTP 403 Forbidden") !== -1);
assertEqual_('rawBody is preserved', result403.rawBody, "Mock raw body content");
console.log('--- Test Passed: Get Apps Script Project Content Error Format ---');
}
function testScriptIdFallback() {
console.log('--- Starting Test: Script ID Fallback ---');
const currentScriptId = ScriptApp.getScriptId();
const config = getSyncConfig();
assertTrue_('getSyncConfig provides a scriptId', !!config.scriptId);
console.log('Current ScriptApp ID: ' + currentScriptId);
console.log('Config Script ID: ' + config.scriptId);
if (config.scriptId === currentScriptId) {
console.log('✅ Fallback working or explicitly matched.');
} else {
console.log('️ Configured scriptId is different from current ScriptApp ID.');
}
console.log('--- Test Passed: Script ID Fallback ---');
}
function testGiteaPullRequestPayload() {
console.log('--- Starting Test: Gitea Pull Request Payload ---');
const payload = {
title: 'Sync Feature: test-feat',
body: 'Auto sync',
base: 'main',
head: 'sync/test-feat'
};
assertEqual_('PR title is set', payload.title, 'Sync Feature: test-feat');
assertEqual_('PR base is main', payload.base, 'main');
assertEqual_('PR head is correct', payload.head, 'sync/test-feat');
console.log('--- Test Passed: Gitea Pull Request Payload ---');
}
function testCheckGiteaPullRequestExistsLogic() {
console.log('--- Starting Test: Check Gitea PR Exists Logic ---');
const mockPrs = [
{ base: { ref: 'main' }, head: { ref: 'sync/feat-1' } },
{ base: { ref: 'main' }, head: { ref: 'sync/feat-2' } }
];
const baseBranch = 'main';
const headBranch = 'sync/feat-2';
const existing = mockPrs.find(function(pr) {
return pr.base && pr.head && pr.base.ref === baseBranch && pr.head.ref === headBranch;
});
assertTrue_('Finds existing PR', !!existing);
assertEqual_('Matches correct head', existing.head.ref, 'sync/feat-2');
const notExisting = mockPrs.find(function(pr) {
return pr.base && pr.head && pr.base.ref === baseBranch && pr.head.ref === 'sync/feat-3';
});
assertTrue_('Does not find missing PR', !notExisting);
console.log('--- Test Passed: Check Gitea PR Exists Logic ---');
}
function testEmptyRepoErrorDetection() {
console.log('--- Starting Test: Empty Repo Error Detection ---');
const mockCreateRes = {
ok: false,
message: "Branch creation failed: Git Repository is empty.",
code: 409
};
const isRepoEmptyError = !mockCreateRes.ok && mockCreateRes.message && mockCreateRes.message.toLowerCase().indexOf('empty') !== -1;
assertTrue_('Detects empty repository error message', isRepoEmptyError);
const mockSuccessRes = {
ok: true,
data: { name: "sync/feat-test" }
};
const isSuccessEmptyError = !mockSuccessRes.ok && mockSuccessRes.message && mockSuccessRes.message.toLowerCase().indexOf('empty') !== -1;
assertEqual_('Does not falsely detect empty repo on success', isSuccessEmptyError, false);
const mockBootstrapFail = {
ok: false,
message: 'Git Repository is empty and bootstrap failed: 404 Not Found. Harap inisialisasi repositori secara manual (misal: centang "Initialize Repository" saat membuat repo).'
};
const detectsManualInit = !mockBootstrapFail.ok && mockBootstrapFail.message.toLowerCase().indexOf('manual') !== -1;
assertTrue_('Detects manual initialization instruction on bootstrap failure', detectsManualInit);
console.log('--- Test Passed: Empty Repo Error Detection ---');
}