Skip to content

Commit 800870c

Browse files
Copilothotlong
andcommitted
fix: resolve TODOs in client SDK filter comment and CLI test glob matching
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 3e3ea16 commit 800870c

2 files changed

Lines changed: 50 additions & 19 deletions

File tree

packages/cli/src/commands/test.ts

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,51 @@ import fs from 'fs';
77
import { QA as CoreQA } from '@objectstack/core';
88
import { QA } from '@objectstack/spec';
99

10+
/**
11+
* Resolve a glob-like pattern to matching file paths.
12+
* Supports `*` (single segment wildcard) and `**` (recursive wildcard).
13+
* Falls back to direct file path if no glob characters are present.
14+
*/
15+
function resolveGlob(pattern: string): string[] {
16+
// Direct file path — no wildcards
17+
if (!pattern.includes('*')) {
18+
return fs.existsSync(pattern) ? [pattern] : [];
19+
}
20+
21+
// Split pattern into the static base directory and the glob portion
22+
const parts = pattern.split(path.sep.replace('\\', '/'));
23+
// Also handle forward-slash on Windows
24+
const segments = pattern.includes('/') ? pattern.split('/') : parts;
25+
26+
let baseDir = '.';
27+
let globStart = 0;
28+
for (let i = 0; i < segments.length; i++) {
29+
if (segments[i].includes('*')) {
30+
globStart = i;
31+
break;
32+
}
33+
baseDir = i === 0 ? segments[i] : path.join(baseDir, segments[i]);
34+
}
35+
36+
if (!fs.existsSync(baseDir)) return [];
37+
38+
// Convert the glob portion into a RegExp
39+
const globPortion = segments.slice(globStart).join('/');
40+
const regexStr = globPortion
41+
.replace(/\./g, '\\.') // escape dots
42+
.replace(/\*\*\//g, '(.+/)?') // ** matches any directory depth
43+
.replace(/\*\*/g, '.*') // trailing ** without slash
44+
.replace(/\*/g, '[^/]*'); // * matches within a single segment
45+
const regex = new RegExp(`^${regexStr}$`);
46+
47+
// Recursively read all files under baseDir
48+
const entries = fs.readdirSync(baseDir, { recursive: true, encoding: 'utf-8' }) as string[];
49+
return entries
50+
.filter(entry => regex.test(entry.replace(/\\/g, '/')))
51+
.map(entry => path.join(baseDir, entry))
52+
.filter(fullPath => fs.statSync(fullPath).isFile());
53+
}
54+
1055
export const testCommand = new Command('test')
1156
.description('Run Quality Protocol test scenarios against a running server')
1257
.argument('[files]', 'Glob pattern for test files (e.g. "qa/*.test.json")', 'qa/*.test.json')
@@ -21,23 +66,8 @@ export const testCommand = new Command('test')
2166
const adapter = new CoreQA.HttpTestAdapter(options.url, options.token);
2267
const runner = new CoreQA.TestRunner(adapter);
2368

24-
// 2. Find Files (Simple implementation for now)
25-
// TODO: Use glob
26-
const cwd = process.cwd();
27-
const testFiles: string[] = [];
28-
29-
// Very basic file finding for demo - assume explicit path or check local dir
30-
if (fs.existsSync(filesPattern)) {
31-
testFiles.push(filesPattern);
32-
} else {
33-
// Simple directory scan
34-
const dir = path.dirname(filesPattern);
35-
const ext = path.extname(filesPattern);
36-
if (fs.existsSync(dir)) {
37-
const files = fs.readdirSync(dir).filter(f => f.endsWith(ext) || f.endsWith('.json'));
38-
files.forEach(f => testFiles.push(path.join(dir, f)));
39-
}
40-
}
69+
// 2. Find test files using glob-style pattern matching
70+
const testFiles: string[] = resolveGlob(filesPattern);
4171

4272
if (testFiles.length === 0) {
4373
console.warn(chalk.yellow(`No test files found matching: ${filesPattern}`));

packages/client/src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,8 +1060,9 @@ export class ObjectStackClient {
10601060

10611061
// 4. Handle Filters (Simple vs AST)
10621062
if (options.filters) {
1063-
// If looks like AST (not plain object map)
1064-
// TODO: robust check. safely assuming map for simplified find, and recommending .query() for AST
1063+
// Detect AST filter format vs simple key-value map. AST filters use an array structure
1064+
// with [field, operator, value] or [logicOp, ...nodes] shape (see isFilterAST).
1065+
// For complex filter expressions, use .query() which builds a proper QueryAST.
10651066
if (this.isFilterAST(options.filters)) {
10661067
queryParams.set('filters', JSON.stringify(options.filters));
10671068
} else {

0 commit comments

Comments
 (0)