From b91c4bf5e7d722c172267541e19efb3cffcc7f72 Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Mon, 11 Aug 2025 23:07:59 +0200 Subject: [PATCH] Enhance test coverage by adding new cases for file listing, reading, and ripgrep functionalities. Implement tests for hidden files, depth handling, and path normalization in list_files tests. Expand read_file tests to cover line skipping and reading from empty files. Improve ripgrep tests with various pattern matching scenarios, including regex handling and file pattern exclusions. --- cli.js | 1 + tests/run-listfiles-tests.js | 40 ++++++++++++++++++++++++ tests/run-readfile-tests.js | 32 +++++++++++++++++++ tests/run-ripgrep-tests.js | 60 ++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+) diff --git a/cli.js b/cli.js index cbae79f..1ef3e26 100644 --- a/cli.js +++ b/cli.js @@ -2,6 +2,7 @@ import 'dotenv/config'; import OpenAI from 'openai'; +//npm install tiktoken //csk-8jftdte6r6vf8fdvp9xkyek5t3jnc6jfhh93d3ewfcwxxvh9 import { promises as fs } from "node:fs"; diff --git a/tests/run-listfiles-tests.js b/tests/run-listfiles-tests.js index 52b1d7a..89ce5d2 100644 --- a/tests/run-listfiles-tests.js +++ b/tests/run-listfiles-tests.js @@ -124,6 +124,46 @@ function cases() { expect: { errorRegex: /Path does not exist:/ } }); + // 7. Hidden excluded when includeHidden=false + list.push({ + name: 'hidden excluded by default', + before: { '.hidden': 'h', 'shown.txt': 's' }, + args: async ({ dir }) => ({ path: path.relative(chrootRoot, dir) || '/', depth: 1, includeHidden: false }), + expect: { cwdFromArgs: true, files: [['shown.txt', 'f', 1]] } + }); + + // 8. Depth 0 shows only top-level entries + list.push({ + name: 'depth 0 top-level only', + before: { 'a.txt': 'A', 'sub/b.txt': 'B' }, + args: async ({ dir }) => ({ path: path.relative(chrootRoot, dir) || '/', depth: 0, includeHidden: false }), + expect: { cwdFromArgs: true, files: [['a.txt', 'f', 1], ['sub', 'd', null]] } + }); + + // 9. Pass hidden file path with includeHidden=false (excluded) + list.push({ + name: 'hidden file path excluded when flag false', + before: { '.only.txt': 'x' }, + args: async ({ dir }) => ({ path: path.relative(chrootRoot, path.join(dir, '.only.txt')), depth: 1, includeHidden: false }), + expect: { cwdFromArgs: 'file', files: [] } + }); + + // 10. Pass hidden file path with includeHidden=true (included) + list.push({ + name: 'hidden file path included when flag true', + before: { '.only.txt': 'x' }, + args: async ({ dir }) => ({ path: path.relative(chrootRoot, path.join(dir, '.only.txt')), depth: 1, includeHidden: true }), + expect: { cwdFromArgs: 'file', files: [['.only.txt', 'f', 1]] } + }); + + // 11. Path normalization outside chroot -> error + list.push({ + name: 'outside chroot error', + before: {}, + args: async () => ({ path: '../../etc', depth: 1, includeHidden: false }), + expect: { errorRegex: /Path escapes chroot boundary/ } + }); + return list; } diff --git a/tests/run-readfile-tests.js b/tests/run-readfile-tests.js index f826058..fe882da 100644 --- a/tests/run-readfile-tests.js +++ b/tests/run-readfile-tests.js @@ -114,6 +114,38 @@ function cases() { expect: { equals: Array.from({ length: 400 }, (_, i) => `L${i + 1}`).join('\n') } }); + // 7. Skip beyond file length -> empty + list.push({ + name: 'skip beyond length returns empty', + before: { 's.txt': 'A\nB' }, + args: async ({ dir }) => ({ path: path.relative(chrootRoot, path.join(dir, 's.txt')), linesToSkip: 10, linesToRead: 5 }), + expect: { equals: '' } + }); + + // 8. Skip to last line and read one + list.push({ + name: 'skip to last line and read one', + before: { 't.txt': 'L1\nL2\nL3' }, + args: async ({ dir }) => ({ path: path.relative(chrootRoot, path.join(dir, 't.txt')), linesToSkip: 2, linesToRead: 1 }), + expect: { equals: 'L3' } + }); + + // 9. Read exactly N lines from middle + list.push({ + name: 'read middle two lines', + before: { 'u.txt': 'A\nB\nC\nD' }, + args: async ({ dir }) => ({ path: path.relative(chrootRoot, path.join(dir, 'u.txt')), linesToSkip: 1, linesToRead: 2 }), + expect: { equals: 'B\nC' } + }); + + // 10. Empty file read -> empty string + list.push({ + name: 'empty file read', + before: { 'empty.txt': '' }, + args: async ({ dir }) => ({ path: path.relative(chrootRoot, path.join(dir, 'empty.txt')), linesToSkip: 0, linesToRead: 100 }), + expect: { equals: '' } + }); + return list; } diff --git a/tests/run-ripgrep-tests.js b/tests/run-ripgrep-tests.js index b919d43..a5b0385 100644 --- a/tests/run-ripgrep-tests.js +++ b/tests/run-ripgrep-tests.js @@ -139,6 +139,66 @@ function cases() { expect: { error: /ripgrep error:/ } }); + // 7. No line numbers (n_flag false) + list.push({ + name: 'no line numbers', + before: { 'a.txt': 'foo\nbar\nfoo' }, + args: async ({ dir }) => ({ pattern: 'foo', filePattern: path.relative(chrootRoot, path.join(dir, '**')), n_flag: false, i_flag: false }), + expect: { equals: [ + `${path.relative(chrootRoot, path.join(sandboxRoot, '07-no-line-numbers/a.txt'))}:foo`, + `${path.relative(chrootRoot, path.join(sandboxRoot, '07-no-line-numbers/a.txt'))}:foo` + ].join('\n') } + }); + + // 8. filePattern include-only to exclude .md (tool supports single -g, so include *.txt) + list.push({ + name: 'filePattern include-only excludes md', + before: { 'a.txt': 'hit', 'b.md': 'hit' }, + args: async ({ dir }) => ({ pattern: 'hit', filePattern: path.relative(chrootRoot, path.join(dir, '**/*.txt')), n_flag: true, i_flag: false }), + expect: { equals: `${path.relative(chrootRoot, path.join(sandboxRoot, '08-filepattern-negation-excludes-md/a.txt'))}:1:hit` } + }); + + // 9. Empty filePattern searches all (we'll scope to the case dir by pattern and path shape) + list.push({ + name: 'empty filePattern searches all', + before: { 'x.js': 'Hello', 'y.txt': 'Hello' }, + args: async ({ dir }) => ({ pattern: 'Hello', filePattern: path.relative(chrootRoot, path.join(dir, '**')), n_flag: true, i_flag: false }), + expect: { equals: [ + `${path.relative(chrootRoot, path.join(sandboxRoot, '09-empty-filepattern-searches-all/x.js'))}:1:Hello`, + `${path.relative(chrootRoot, path.join(sandboxRoot, '09-empty-filepattern-searches-all/y.txt'))}:1:Hello` + ].join('\n') } + }); + + // 10. Anchored regex + list.push({ + name: 'anchored regex', + before: { 'a.txt': 'Hello\nHello world\nHello' }, + args: async ({ dir }) => ({ pattern: '^Hello$', filePattern: path.relative(chrootRoot, path.join(dir, '**')), n_flag: true, i_flag: false }), + expect: { equals: [ + `${path.relative(chrootRoot, path.join(sandboxRoot, '10-anchored-regex/a.txt'))}:1:Hello`, + `${path.relative(chrootRoot, path.join(sandboxRoot, '10-anchored-regex/a.txt'))}:3:Hello` + ].join('\n') } + }); + + // 11. Special regex characters + list.push({ + name: 'special regex characters', + before: { 'a.txt': 'a+b?c\\d and a+b?c\\d' }, + args: async ({ dir }) => ({ pattern: 'a\\+b\\?c\\\\d', filePattern: path.relative(chrootRoot, path.join(dir, '**')), n_flag: true, i_flag: false }), + expect: { equals: `${path.relative(chrootRoot, path.join(sandboxRoot, '11-special-regex-characters/a.txt'))}:1:a+b?c\\d and a+b?c\\d` } + }); + + // 12. Multiple files across dirs deterministic order + list.push({ + name: 'multi dirs deterministic', + before: { 'b/b.txt': 'X', 'a/a.txt': 'X' }, + args: async ({ dir }) => ({ pattern: '^X$', filePattern: path.relative(chrootRoot, path.join(dir, '**')), n_flag: true, i_flag: false }), + expect: { equals: [ + `${path.relative(chrootRoot, path.join(sandboxRoot, '12-multi-dirs-deterministic/a/a.txt'))}:1:X`, + `${path.relative(chrootRoot, path.join(sandboxRoot, '12-multi-dirs-deterministic/b/b.txt'))}:1:X` + ].join('\n') } + }); + return list; }