Skip to content

Commit 74cf85f

Browse files
Copilotvobu
andcommitted
fix: search incidents by errorMessage with wildcard returns no results
The Camunda 8 incident search API does not support $like filter objects for the errorMessage field. When a wildcard pattern was passed via --errorMessage, toStringFilter() converted it to { $like: '...' }, which the API silently ignored, returning 0 results. Fix: detect wildcard patterns in errorMessage and fall back to client-side filtering (fetching up to CI_PAGE_SIZE results and filtering locally using case-sensitive pattern matching). Plain strings (no wildcards) are still passed to the API for server-side exact matching as before. Also adds: - matchesCaseSensitive helper for case-sensitive wildcard matching - wildcardToRegex optional caseInsensitive parameter (default true) - Unit tests for new case-sensitive matching behaviour Co-authored-by: vobu <6573426+vobu@users.noreply.github.com>
1 parent 0dbe01e commit 74cf85f

File tree

2 files changed

+67
-11
lines changed

2 files changed

+67
-11
lines changed

src/commands/search.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const toStringFilter = (value: string): string | { $like: string } =>
2727
* Convert a wildcard pattern (* and ?) to a case-insensitive RegExp.
2828
* Handles escaped wildcards (\* and \?).
2929
*/
30-
export const wildcardToRegex = (pattern: string): RegExp => {
30+
export const wildcardToRegex = (pattern: string, caseInsensitive = true): RegExp => {
3131
let regex = '';
3232
for (let i = 0; i < pattern.length; i++) {
3333
if (pattern[i] === '\\' && i + 1 < pattern.length && (pattern[i + 1] === '*' || pattern[i + 1] === '?')) {
@@ -41,7 +41,7 @@ export const wildcardToRegex = (pattern: string): RegExp => {
4141
regex += pattern[i].replace(/[[\]{}()+.,\\^$|#]/g, '\\$&');
4242
}
4343
}
44-
return new RegExp(`^${regex}$`, 'i');
44+
return new RegExp(`^${regex}$`, caseInsensitive ? 'i' : '');
4545
};
4646

4747
/**
@@ -53,6 +53,15 @@ export const matchesCaseInsensitive = (value: string | undefined | null, pattern
5353
return wildcardToRegex(pattern).test(value);
5454
};
5555

56+
/**
57+
* Test if a value matches a wildcard pattern case-sensitively.
58+
* Without wildcards, performs exact case-sensitive match.
59+
*/
60+
export const matchesCaseSensitive = (value: string | undefined | null, pattern: string): boolean => {
61+
if (value == null) return false;
62+
return wildcardToRegex(pattern, false).test(value);
63+
};
64+
5665
const toBigIntSafe = (value: unknown): bigint => {
5766
try {
5867
return BigInt(String(value));
@@ -431,7 +440,9 @@ export async function searchIncidents(options: {
431440
const logger = getLogger();
432441
const client = createClient(options.profile);
433442
const tenantId = resolveTenantId(options.profile);
434-
const hasCiFilter = !!(options.iErrorMessage || options.iProcessDefinitionId);
443+
// The incident API does not support a $like filter for errorMessage; fall back to client-side filtering for wildcard patterns
444+
const errorMessageHasWildcard = !!(options.errorMessage && hasUnescapedWildcard(options.errorMessage));
445+
const hasCiFilter = !!(options.iErrorMessage || options.iProcessDefinitionId || errorMessageHasWildcard);
435446

436447
// Build search criteria description for user feedback
437448
const criteria: string[] = [];
@@ -486,8 +497,8 @@ export async function searchIncidents(options: {
486497
filter.filter.errorType = options.errorType;
487498
}
488499

489-
if (options.errorMessage) {
490-
filter.filter.errorMessage = toStringFilter(options.errorMessage);
500+
if (options.errorMessage && !errorMessageHasWildcard) {
501+
filter.filter.errorMessage = options.errorMessage;
491502
}
492503

493504
if (options.processDefinitionId) {
@@ -500,6 +511,7 @@ export async function searchIncidents(options: {
500511
result.items = result.items.filter((incident: any) => {
501512
if (options.iErrorMessage && !matchesCaseInsensitive(incident.errorMessage, options.iErrorMessage)) return false;
502513
if (options.iProcessDefinitionId && !matchesCaseInsensitive(incident.processDefinitionId, options.iProcessDefinitionId)) return false;
514+
if (errorMessageHasWildcard && options.errorMessage && !matchesCaseSensitive(incident.errorMessage, options.errorMessage)) return false;
503515
return true;
504516
});
505517
}

tests/unit/search-wildcard.test.ts

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import { test, describe } from 'node:test';
66
import assert from 'node:assert';
7-
import { toStringFilter, wildcardToRegex, matchesCaseInsensitive, hasUnescapedWildcard } from '../../src/commands/search.ts';
7+
import { toStringFilter, wildcardToRegex, matchesCaseInsensitive, matchesCaseSensitive, hasUnescapedWildcard } from '../../src/commands/search.ts';
88

99
describe('toStringFilter — wildcard detection', () => {
1010
test('plain string returns the string as-is', () => {
@@ -111,11 +111,55 @@ describe('wildcardToRegex — pattern conversion', () => {
111111
assert.ok(!regex.test('myXprocessXv1'));
112112
});
113113

114-
test('complex mixed pattern', () => {
115-
const regex = wildcardToRegex('*-v?.bpmn');
116-
assert.ok(regex.test('process-v1.bpmn'));
117-
assert.ok(regex.test('PROCESS-V2.BPMN'));
118-
assert.ok(!regex.test('process-v12.bpmn'));
114+
test('case-sensitive mode rejects wrong case', () => {
115+
const regex = wildcardToRegex('hello', false);
116+
assert.ok(regex.test('hello'));
117+
assert.ok(!regex.test('HELLO'));
118+
assert.ok(!regex.test('Hello'));
119+
});
120+
121+
test('case-sensitive mode with wildcard', () => {
122+
const regex = wildcardToRegex('Assertion*', false);
123+
assert.ok(regex.test('Assertion failure'));
124+
assert.ok(!regex.test('assertion failure'));
125+
assert.ok(!regex.test('ASSERTION FAILURE'));
126+
});
127+
});
128+
129+
describe('matchesCaseSensitive — value matching', () => {
130+
test('exact match requires correct case', () => {
131+
assert.ok(matchesCaseSensitive('OrderProcess', 'OrderProcess'));
132+
assert.ok(!matchesCaseSensitive('OrderProcess', 'orderprocess'));
133+
assert.ok(!matchesCaseSensitive('OrderProcess', 'ORDERPROCESS'));
134+
});
135+
136+
test('wildcard match respects case', () => {
137+
assert.ok(matchesCaseSensitive('Assertion failure on evaluate', 'Assertion*'));
138+
assert.ok(!matchesCaseSensitive('Assertion failure on evaluate', 'assertion*'));
139+
assert.ok(matchesCaseSensitive('Assertion failure on evaluate', '*failure*'));
140+
assert.ok(!matchesCaseSensitive('Assertion failure on evaluate', '*FAILURE*'));
141+
});
142+
143+
test('returns false for null or undefined', () => {
144+
assert.ok(!matchesCaseSensitive(null, 'test'));
145+
assert.ok(!matchesCaseSensitive(undefined, 'test'));
146+
});
147+
148+
test('returns false when pattern does not match', () => {
149+
assert.ok(!matchesCaseSensitive('OrderProcess', 'payment*'));
150+
assert.ok(!matchesCaseSensitive('hello', 'world'));
151+
});
152+
153+
test('empty pattern matches empty value', () => {
154+
assert.ok(matchesCaseSensitive('', ''));
155+
});
156+
157+
test('empty pattern does not match non-empty value', () => {
158+
assert.ok(!matchesCaseSensitive('hello', ''));
159+
});
160+
161+
test('non-empty pattern does not match empty value', () => {
162+
assert.ok(!matchesCaseSensitive('', 'hello'));
119163
});
120164
});
121165

0 commit comments

Comments
 (0)