diff --git a/.gitignore b/.gitignore index e703052..95cca36 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ .env .env.* *.env +!.env.example +!.env.sample +!.env.template # Logs logs/ diff --git a/test/smoke.test.js b/test/smoke.test.js index 8c1f97c..e34e69b 100644 --- a/test/smoke.test.js +++ b/test/smoke.test.js @@ -2,39 +2,64 @@ const { test } = require('node:test'); const assert = require('node:assert/strict'); const { spawnSync } = require('node:child_process'); const fs = require('node:fs'); +const path = require('node:path'); + +function collectJsFiles(rootDir) { + const files = []; + const skipDirs = new Set(['.git', 'node_modules', 'data', 'logs']); + + function walk(dir) { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + if (entry.isDirectory()) { + if (!skipDirs.has(entry.name)) walk(path.join(dir, entry.name)); + continue; + } + if (entry.isFile() && entry.name.endsWith('.js')) files.push(path.join(dir, entry.name)); + } + } + + walk(rootDir); + return files; +} + +function extractLiteralIds(source, kind) { + const regex = kind === 'callback' + ? /Markup\.button\.callback\([^,]+,\s*(["'`])((?:\\.|(?!\1).)*)\1\)/g + : /bot\.action\(\s*(["'`])((?:\\.|(?!\1).)*)\1/g; + return new Set(Array.from(source.matchAll(regex), (m) => m[2])); +} + +function extractActionRegexPatterns(source) { + const patterns = []; + const matches = source.matchAll(/bot\.action\(\s*\/(.+?)\/([dgimsuvy]*)/g); + for (const m of matches) { + try { + patterns.push(new RegExp(m[1], m[2])); + } catch (_) { + // Skip malformed patterns in this static smoke check. + } + } + return patterns; +} test('index.js syntax is valid', () => { const result = spawnSync(process.execPath, ['--check', 'index.js'], { encoding: 'utf8' }); assert.equal(result.status, 0, result.stderr || result.stdout); }); -test('menu callback buttons map to handlers (literal or known dynamic patterns)', () => { - const source = fs.readFileSync('index.js', 'utf8'); - const callbackButtons = new Set(Array.from(source.matchAll(/Markup\.button\.callback\([^,]+,\s*'([^']+)'\)/g), (m) => m[1])); - const actionHandlers = new Set(Array.from(source.matchAll(/bot\.action\('([^']+)'/g), (m) => m[1])); - - const dynamicPatterns = [ - /^promo_open_/, - /^promo_claim_/, - /^pamenu_gw_end_/, - /^pamenu_gw_extend_/, - /^pamenu_gw_pause_/, - /^pamenu_gw_resume_/, - /^pamenu_gw_redraw_/, - /^admin_stats_/, - /^admin_dash_page_/, - /^menu_page_/, - /^pmenu_walk_nav_/, - /^walk_/, - /^gw_/, - /^tip_/, - /^tgw_dur_/, - ]; +test('menu callback buttons map to handlers (literal or dynamic patterns)', () => { + const jsFiles = collectJsFiles('.'); + const combinedSource = jsFiles + .map((file) => fs.readFileSync(file, 'utf8')) + .join('\n\n'); + + const callbackButtons = extractLiteralIds(combinedSource, 'callback'); + const literalActionHandlers = extractLiteralIds(combinedSource, 'action'); + const dynamicActionPatterns = extractActionRegexPatterns(combinedSource); const uncovered = Array.from(callbackButtons) - .filter((id) => !actionHandlers.has(id) && !dynamicPatterns.some((pattern) => pattern.test(id))) + .filter((id) => !literalActionHandlers.has(id) && !dynamicActionPatterns.some((pattern) => pattern.test(id))) .sort(); assert.deepEqual(uncovered, [], `Uncovered callback handlers: ${uncovered.join(', ')}`); }); -