Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
.env
.env.*
*.env
!.env.example
!.env.sample
!.env.template

# Logs
logs/
Expand Down
73 changes: 49 additions & 24 deletions test/smoke.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The smoke test currently treats callback IDs built with template literals (e.g., containing ${...}) as literal IDs and then tries to match them against regex-based handlers, which will always fail, causing valid dynamically-generated callback buttons like pamenu_gw_end_${gw.id} or help_page_${page - 1} to be reported as "uncovered" even though matching bot.action(/.../) handlers exist; restricting the callback scan to only single/double-quoted literals avoids these false positives while still checking all statically-known IDs. [logic error]

Severity Level: Major ⚠️
- ❌ Smoke test fails despite valid dynamic callback handlers.
- ⚠️ CI/test pipeline may be blocked by false alarms.
- ⚠️ Reduces trust in static callback-to-handler coverage check.
Suggested change
? /Markup\.button\.callback\([^,]+,\s*(["'`])((?:\\.|(?!\1).)*)\1\)/g
? /Markup\.button\.callback\([^,]+,\s*(["'])((?:\\.|(?!\1).)*)\1\)/g
Steps of Reproduction ✅
1. Run the smoke tests, e.g. `node test/smoke.test.js` or the project's test script, which
executes `test('menu callback buttons map to handlers (literal or dynamic patterns)',
...)` in `test/smoke.test.js:50-65`.

2. That test reads all JS sources via `collectJsFiles('.')` and concatenates them into
`combinedSource`, then calls `extractLiteralIds(combinedSource, 'callback')` at
`test/smoke.test.js:56`, using the current regex that matches Markup.button.callback IDs
quoted with single, double, or backtick literals.

3. In `index.js:2293-2294`, callback buttons are defined with template literals for their
callback data, e.g. `Markup.button.callback(#${gw.id} End Early, pamenu_gw_end_${gw.id})`
and similarly for extend/cancel/participants, and in `index.js:3046-3048` help pagination
buttons are defined as `help_page_${page - 1}` / `help_page_${page + 1}`.

4. `extractLiteralIds` currently treats those backtick template literals as literal IDs
(e.g. the string `pamenu_gw_end_${gw.id}`), but when the test compares them against
handlers, the corresponding regex handlers like `bot.action(/^pamenu_gw_end_(\d+)$/, ...)`
at `index.js:4839` and `bot.action(/^help_page_(\d+)$/, ...)` at `index.js:4483` do not
match those literal template strings, so they appear in `uncovered` and cause the
assertion at `test/smoke.test.js:64` to fail with "Uncovered callback handlers:
pamenu_gw_end_${gw.id}, help_page_${page - 1}, ...".
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** test/smoke.test.js
**Line:** 27:27
**Comment:**
	*Logic Error: The smoke test currently treats callback IDs built with template literals (e.g., containing `${...}`) as literal IDs and then tries to match them against regex-based handlers, which will always fail, causing valid dynamically-generated callback buttons like `pamenu_gw_end_${gw.id}` or `help_page_${page - 1}` to be reported as "uncovered" even though matching `bot.action(/.../)` handlers exist; restricting the callback scan to only single/double-quoted literals avoids these false positives while still checking all statically-known IDs.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

: /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(', ')}`);
});