Skip to content

Commit 9a4f0b7

Browse files
tests
1 parent 829f8d6 commit 9a4f0b7

2 files changed

Lines changed: 207 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { parseDissectFieldTokens, DissectColorManager } from './dissect_color_manager';
9+
10+
describe('parseDissectFieldTokens', () => {
11+
it('tracks startIndex and endIndex for each token', () => {
12+
const tokens = parseDissectFieldTokens('[%{ts}] %{level} - %{msg}');
13+
14+
expect(tokens).toHaveLength(3);
15+
expect(tokens[0]).toMatchObject({ fieldName: 'ts', startIndex: 1, endIndex: 6 });
16+
expect(tokens[1]).toMatchObject({ fieldName: 'level', startIndex: 8, endIndex: 16 });
17+
expect(tokens[2]).toMatchObject({ fieldName: 'msg', startIndex: 19, endIndex: 25 });
18+
});
19+
20+
it('includes skip and reference keys (unlike @kbn/streamlang parseDissectPattern which filters them)', () => {
21+
const tokens = parseDissectFieldTokens('%{a} %{?skip} %{*ref} %{&ref}');
22+
23+
expect(tokens).toHaveLength(4);
24+
expect(tokens[1]).toMatchObject({ fieldName: 'skip', modifier: '?' });
25+
expect(tokens[2]).toMatchObject({ fieldName: 'ref', modifier: '*' });
26+
expect(tokens[3]).toMatchObject({ fieldName: 'ref', modifier: '&' });
27+
});
28+
});
29+
30+
describe('DissectColorManager', () => {
31+
it('assigns colors deterministically in palette order', () => {
32+
const manager = new DissectColorManager();
33+
manager.updatePattern('%{a} %{b} %{c}');
34+
const map = manager.getFieldColourMap();
35+
36+
expect(map.get('a')).toBe('Primary');
37+
expect(map.get('b')).toBe('Accent');
38+
expect(map.get('c')).toBe('AccentSecondary');
39+
});
40+
41+
it('assigns the same color to duplicate field names', () => {
42+
const manager = new DissectColorManager();
43+
manager.updatePattern('%{a} %{b} %{a}');
44+
const map = manager.getFieldColourMap();
45+
46+
expect(map.size).toBe(2);
47+
expect(map.get('a')).toBe('Primary');
48+
expect(map.get('b')).toBe('Accent');
49+
});
50+
51+
it('removes colors for fields no longer in the pattern', () => {
52+
const manager = new DissectColorManager();
53+
manager.updatePattern('%{a} %{b} %{c}');
54+
expect(manager.getFieldColourMap().size).toBe(3);
55+
56+
manager.updatePattern('%{a} %{c}');
57+
const map = manager.getFieldColourMap();
58+
59+
expect(map.size).toBe(2);
60+
expect(map.has('b')).toBe(false);
61+
expect(map.has('a')).toBe(true);
62+
expect(map.has('c')).toBe(true);
63+
});
64+
65+
it('transfers color when a field is fully replaced at the same position', () => {
66+
const manager = new DissectColorManager();
67+
manager.updatePattern('%{a} %{b}');
68+
const colorA = manager.getFieldColourMap().get('a');
69+
70+
manager.updatePattern('%{renamed} %{b}');
71+
const map = manager.getFieldColourMap();
72+
73+
expect(map.get('renamed')).toBe(colorA);
74+
expect(map.has('a')).toBe(false);
75+
});
76+
77+
it('assigns a new color when renaming one of several duplicate fields', () => {
78+
const manager = new DissectColorManager();
79+
manager.updatePattern('%{+ts} %{+ts} %{+ts} %{host}');
80+
const colorTs = manager.getFieldColourMap().get('ts');
81+
const colorHost = manager.getFieldColourMap().get('host');
82+
83+
manager.updatePattern('%{+ts2} %{+ts} %{+ts} %{host}');
84+
const map = manager.getFieldColourMap();
85+
86+
expect(map.get('ts')).toBe(colorTs);
87+
expect(map.get('host')).toBe(colorHost);
88+
expect(map.get('ts2')).not.toBe(colorTs);
89+
});
90+
91+
it('keeps existing colors stable when new fields are added', () => {
92+
const manager = new DissectColorManager();
93+
manager.updatePattern('%{a} %{b}');
94+
const colorA = manager.getFieldColourMap().get('a');
95+
const colorB = manager.getFieldColourMap().get('b');
96+
97+
manager.updatePattern('%{a} %{b} %{c}');
98+
const map = manager.getFieldColourMap();
99+
100+
expect(map.get('a')).toBe(colorA);
101+
expect(map.get('b')).toBe(colorB);
102+
expect(map.has('c')).toBe(true);
103+
});
104+
105+
it('cycles through the palette when more fields than colors', () => {
106+
const manager = new DissectColorManager();
107+
const fields = Array.from({ length: 10 }, (_, i) => `%{f${i}}`).join(' ');
108+
manager.updatePattern(fields);
109+
const map = manager.getFieldColourMap();
110+
111+
expect(map.size).toBe(10);
112+
expect(map.get('f0')).toBe(map.get('f8'));
113+
});
114+
115+
it('clears all colors when pattern becomes empty', () => {
116+
const manager = new DissectColorManager();
117+
manager.updatePattern('%{a} %{b}');
118+
expect(manager.getFieldColourMap().size).toBe(2);
119+
120+
manager.updatePattern('');
121+
expect(manager.getFieldColourMap().size).toBe(0);
122+
});
123+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { dissectPatternToRegex } from './dissect_sample';
9+
10+
describe('dissectPatternToRegex', () => {
11+
it('returns null for pattern with no tokens', () => {
12+
expect(dissectPatternToRegex('')).toBeNull();
13+
expect(dissectPatternToRegex('just plain text')).toBeNull();
14+
});
15+
16+
it('provides correct group-to-field-name mapping', () => {
17+
const result = dissectPatternToRegex('%{clientip} %{method} %{status}');
18+
19+
expect(result!.groupToFieldName.get('_f0')).toBe('clientip');
20+
expect(result!.groupToFieldName.get('_f1')).toBe('method');
21+
expect(result!.groupToFieldName.get('_f2')).toBe('status');
22+
});
23+
24+
it('provides match indices via the d flag', () => {
25+
const result = dissectPatternToRegex('%{a} %{b}');
26+
const match = result!.regex.exec('hello world');
27+
28+
expect(match!.indices!.groups!._f0).toEqual([0, 5]);
29+
expect(match!.indices!.groups!._f1).toEqual([6, 11]);
30+
});
31+
32+
it('escapes regex special characters in delimiters', () => {
33+
const result = dissectPatternToRegex('[%{ts}] %{msg}');
34+
const match = result!.regex.exec('[2024-01-01] hello world');
35+
36+
expect(match!.groups!._f0).toBe('2024-01-01');
37+
expect(match!.groups!._f1).toBe('hello world');
38+
});
39+
40+
it('makes the last token greedy when no trailing literal', () => {
41+
const result = dissectPatternToRegex('%{a} %{b}');
42+
const match = result!.regex.exec('x y z w');
43+
44+
expect(match!.groups!._f0).toBe('x');
45+
expect(match!.groups!._f1).toBe('y z w');
46+
});
47+
48+
it('makes the last token non-greedy when there is trailing literal', () => {
49+
const result = dissectPatternToRegex('%{a} %{b};');
50+
const match = result!.regex.exec('x y;');
51+
52+
expect(match!.groups!._f0).toBe('x');
53+
expect(match!.groups!._f1).toBe('y');
54+
});
55+
56+
it('handles a real-world Apache log pattern', () => {
57+
const result = dissectPatternToRegex('%{ip} - - [%{ts}] "%{request}" %{status} %{size}');
58+
const sample = '55.3.244.1 - - [2024-01-01T00:00:00Z] "GET /index.html HTTP/1.1" 200 1234';
59+
const match = result!.regex.exec(sample);
60+
61+
expect(match!.groups!._f0).toBe('55.3.244.1');
62+
expect(match!.groups!._f1).toBe('2024-01-01T00:00:00Z');
63+
expect(match!.groups!._f2).toBe('GET /index.html HTTP/1.1');
64+
expect(match!.groups!._f3).toBe('200');
65+
expect(match!.groups!._f4).toBe('1234');
66+
});
67+
68+
it('uses index-based group names so duplicate field names do not collide', () => {
69+
const result = dissectPatternToRegex('%{+name/1} %{+name/2}');
70+
const match = result!.regex.exec('John Doe');
71+
72+
expect(result!.groupToFieldName.get('_f0')).toBe('name');
73+
expect(result!.groupToFieldName.get('_f1')).toBe('name');
74+
expect(match!.groups!._f0).toBe('John');
75+
expect(match!.groups!._f1).toBe('Doe');
76+
});
77+
78+
it('uses index-based group names so special characters in field names are safe', () => {
79+
const result = dissectPatternToRegex('%{@timestamp} %{host.name}');
80+
81+
expect(result!.groupToFieldName.get('_f0')).toBe('@timestamp');
82+
expect(result!.groupToFieldName.get('_f1')).toBe('host.name');
83+
});
84+
});

0 commit comments

Comments
 (0)