Skip to content

Commit 92aab82

Browse files
authored
Merge pull request #40 from CVEProject/hk/006_exact_phrase_search
Step 1 in implementing Exact Phrase search
2 parents 22d57ad + 3a5e766 commit 92aab82

File tree

6 files changed

+164
-55
lines changed

6 files changed

+164
-55
lines changed

src/search/SearchQueryBuilder.test.unit.ts

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,66 @@ describe(`SearchQueryBuilder`, () => {
1212

1313
// ----- constructor
1414

15-
it(`constructor(simpleString) correctly sets all fields with proper defaults for options`, async () => {
15+
it(`constructor(simpleString) correctly sets fields with proper defaults for options`, async () => {
1616
const builder = new SearchQueryBuilder(kSimpleSearchString)
1717
expect(builder._searchText).toBe(kSimpleSearchString)
18-
expect(builder._searchOptions.track_total_hits).toBeTruthy()
18+
const options = builder._searchOptions;
19+
expect(options.useCache).toBeTruthy();
20+
expect(options.track_total_hits).toBeTruthy();
21+
expect(options.default_operator).toBe('AND');
22+
expect(options.metadataOnly).toBeFalsy();
23+
expect(options.fields.length).toBe(0);
24+
expect(options.sort.length).toBe(1);
25+
expect(options.sort).toMatchSnapshot();
26+
expect(options.from).toBe(0);
27+
expect(options.size).toBe(SearchQueryBuilder.kDefaultNumResults)
1928
});
2029

2130

22-
it(`constructor(simpleString,{track_total_hits:value}) correctly sets all fields with specified options`, async () => {
23-
const builder = new SearchQueryBuilder(kSimpleSearchString, { track_total_hits: true })
24-
expect(builder._searchText).toBe(kSimpleSearchString)
31+
it(`constructor(simpleString,<options>) correctly sets fields with specified options`, async () => {
32+
let builder;
33+
// track_total_hits
34+
builder = new SearchQueryBuilder("", { track_total_hits: true })
2535
expect(builder._searchOptions.track_total_hits).toBeTruthy()
26-
27-
const req2 = new SearchQueryBuilder(kSimpleSearchString, { track_total_hits: false })
28-
expect(req2._searchText).toBe(kSimpleSearchString)
29-
expect(req2._searchOptions.track_total_hits).toBeFalsy()
36+
builder = new SearchQueryBuilder("", { track_total_hits: false });
37+
expect(builder._searchOptions.track_total_hits).toBeFalsy();
38+
39+
// metadataOnly
40+
builder = new SearchQueryBuilder("", { metadataOnly: true });
41+
expect(builder._searchOptions.metadataOnly).toBeTruthy();
42+
builder = new SearchQueryBuilder("", { metadataOnly: false });
43+
expect(builder._searchOptions.metadataOnly).toBeFalsy()
44+
45+
// fileds
46+
builder = new SearchQueryBuilder("", { fields: [] });
47+
expect(builder._searchOptions.fields.length).toBe(0);
48+
expect(builder._searchOptions.fields).toMatchSnapshot();
49+
builder = new SearchQueryBuilder("", { fields: ["containers.cna"] });
50+
expect(builder._searchOptions.fields).toMatchSnapshot();
51+
builder = new SearchQueryBuilder("", { fields: ["containers.adp", "containers.cna", "z", "xy", "ab"] }); // order should be preserved
52+
expect(builder._searchOptions.fields).toMatchSnapshot();
53+
54+
55+
// sort
56+
builder = new SearchQueryBuilder("", { sort: [] });
57+
expect(builder._searchOptions.sort).toMatchSnapshot();
58+
builder = new SearchQueryBuilder("", {
59+
sort: [{
60+
"cveMetadata.cveId.keyword": { "order": "asc" }
61+
}]
62+
});
63+
expect(builder._searchOptions.sort).toMatchSnapshot();
64+
builder = new SearchQueryBuilder("", {
65+
sort: [
66+
{
67+
"cveMetadata.dateUpdated": { "order": "desc" }
68+
},
69+
{
70+
"cveMetadata.cveId.keyword": { "order": "desc" }
71+
}
72+
]
73+
}); // order should be preserved
74+
expect(builder._searchOptions.sort).toMatchSnapshot()
3075
});
3176

3277

src/search/SearchQueryBuilder.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import { SearchRequest } from './SearchRequest.js';
88
*/
99
export class SearchQueryBuilder {
1010

11+
/** default number of results to return when not specified */
12+
static kDefaultNumResults = 25
13+
1114
/** the user entered text */
1215
_searchText: string;
1316

@@ -33,8 +36,14 @@ export class SearchQueryBuilder {
3336
"cveMetadata.cveId.keyword": { "order": "desc" }
3437
}],
3538
from: options?.from ?? 0,
36-
size: options?.size ?? 25,
39+
size: options?.size ?? SearchQueryBuilder.kDefaultNumResults,
3740
};
41+
if (this._searchOptions.from < 0) {
42+
this._searchOptions.from = 0;
43+
}
44+
if (this._searchOptions.size < this._searchOptions.from) {
45+
this._searchOptions.size = this._searchOptions.from + 1;
46+
}
3847
this._searchRequest = new SearchRequest(searchText)
3948
}
4049

src/search/SearchRequest.test.unit.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,6 @@ describe(`SearchRequest`, () => {
183183
// ----- disallowed strings -----
184184
// ["CVE–1999–0001", 'SEARCH_GENERAL_TEXT', "CVE–1999–0001"],
185185

186-
["127.0.0.*", 'SEARCH_AS_WILDCARD_ASTERISK', "127.0.0.*"],
187-
// [".127.0.0.*", 'SEARCH_AS_WILDCARD_ASTERISK', ".127.0.0.*"],
188-
// [".127.0.0.???", 'WILDCARD_QUESTION_SEARCH_NOT_SUPPORTED', ".127.0.0.???"],
189-
// [".127.0.0.*", 'SEARCH_AS_WILDCARD_ASTERISK', ".127.0.0.*"],
190-
191186
// // ----- simple search strings -----
192187
// ["2020", 'SEARCH_GENERAL_TEXT', "2020"],
193188
// ["office", 'SEARCH_GENERAL_TEXT', "office"],
@@ -345,16 +340,9 @@ describe(`SearchRequest`, () => {
345340
[`"man in the middle"`, [`man in the middle`]],
346341
[`man in the middle`, ["man", "in", "the", "middle"]],
347342
[``, []],
348-
[`"`, [`"`]],
343+
[`"`, [``]],
349344
// [`""`, [``]], //@todo
350345
// [`"""`, [`"`]], //@todo
351-
// [`"micro????"`, [`"micro????"`]], // @todo
352-
[`micro*`, [`micro*`]],
353-
[`*micro*`, [`*micro*`]],
354-
[`*micro**`, [`*micro**`]],
355-
[`*micro *`, [`*micro`, `*`]],
356-
[`micro????`, [`micro????`]],
357-
[`micro*????`, [`micro*????`]],
358346
// ----- UTF codes -----
359347
]
360348
.forEach((test: [string, string[]]) => {

src/search/__snapshots__/SearchQueryBuilder.test.unit.ts.snap

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,58 @@ SearchRequest {
308308
"_searchText": "office",
309309
}
310310
`;
311+
312+
exports[`SearchQueryBuilder constructor(simpleString) correctly sets fields with proper defaults for options 1`] = `
313+
Array [
314+
Object {
315+
"cveMetadata.cveId.keyword": Object {
316+
"order": "desc",
317+
},
318+
},
319+
]
320+
`;
321+
322+
exports[`SearchQueryBuilder constructor(simpleString,<options>) correctly sets fields with specified options 1`] = `Array []`;
323+
324+
exports[`SearchQueryBuilder constructor(simpleString,<options>) correctly sets fields with specified options 2`] = `
325+
Array [
326+
"containers.cna",
327+
]
328+
`;
329+
330+
exports[`SearchQueryBuilder constructor(simpleString,<options>) correctly sets fields with specified options 3`] = `
331+
Array [
332+
"containers.adp",
333+
"containers.cna",
334+
"z",
335+
"xy",
336+
"ab",
337+
]
338+
`;
339+
340+
exports[`SearchQueryBuilder constructor(simpleString,<options>) correctly sets fields with specified options 4`] = `Array []`;
341+
342+
exports[`SearchQueryBuilder constructor(simpleString,<options>) correctly sets fields with specified options 5`] = `
343+
Array [
344+
Object {
345+
"cveMetadata.cveId.keyword": Object {
346+
"order": "asc",
347+
},
348+
},
349+
]
350+
`;
351+
352+
exports[`SearchQueryBuilder constructor(simpleString,<options>) correctly sets fields with specified options 6`] = `
353+
Array [
354+
Object {
355+
"cveMetadata.dateUpdated": Object {
356+
"order": "desc",
357+
},
358+
},
359+
Object {
360+
"cveMetadata.cveId.keyword": Object {
361+
"order": "desc",
362+
},
363+
},
364+
]
365+
`;

src/search/test_cases/search_ipv4.test.e2e.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ describe('IPv4 Searches', () => {
88
{ input: "10.0.0.1" }, // class A
99
{ input: "172.16.0.1" }, // class B
1010
{ input: "255.255.255.255" }, // broadcast
11+
{ input: "0.0.0.0" }, // non-routable, special IP
1112
];
1213

1314
testCases.forEach(({ input }) => {
@@ -23,6 +24,7 @@ describe('IPv4 Searches', () => {
2324
{ counterInput: "wikipedia" }, // google search bar style "url"
2425
{ counterInput: "localhost" },
2526
{ counterInput: "127.0.0.1:65535" }, // localhost with user port is treated as URL
27+
{ counterInput: "1.2.3.4.5", expectedType: 'SEARCH_AS_VERSION' }, // too many periods
2628
{ counterInput: "http://user:pass@127.0.0.1/?a=b&abc=1%22#25", expectedType: 'SEARCH_AS_URL'},
2729
{ counterInput: "::", expectedType: 'SEARCH_AS_IPv6'},
2830
{ counterInput: "2001:db8:3333:4444:5555:6666:1.2.3.4", expectedType: 'SEARCH_AS_IPv6'},
@@ -32,6 +34,8 @@ describe('IPv4 Searches', () => {
3234
// using * as wildcard
3335
{ counterInput: "127.0.0.*", expectedType: 'SEARCH_AS_WILDCARD_ASTERISK' },
3436
{ counterInput: "*.0.0.1", expectedType: 'SEARCH_AS_WILDCARD_ASTERISK' },
37+
// cidr
38+
{ counterInput: "172.16.0.1/32"/*, expectedType: 'SEARCH_AS_URL'*/ }, // this should be CIDR
3539
];
3640

3741
antiCases.forEach(({ counterInput, expectedType }) => {
@@ -98,19 +102,19 @@ describe('SearchRequest testing IPv6', () => {
98102
];
99103

100104
antiCases.forEach(({ counterInput, expectedType }) => {
101-
it(`isUrl('${counterInput}') --> false`, () => {
102-
const result = SearchRequest.isIpV6String(counterInput);
103-
expect(result).toBe(false);
104-
});
105+
it(`isUrl('${counterInput}') --> false`, () => {
106+
const result = SearchRequest.isIpV6String(counterInput);
107+
expect(result).toBe(false);
108+
});
105109
});
106110

107111
antiCases.forEach(({ counterInput, expectedType }) => {
108-
if (expectedType) {
109-
it(`findSearchRequestType('${counterInput}') --> ${expectedType}`, () => {
110-
const result = SearchRequest.findSearchRequestType(counterInput);
111-
expect(result).toBe(expectedType);
112-
});
113-
}
112+
if (expectedType) {
113+
it(`findSearchRequestType('${counterInput}') --> ${expectedType}`, () => {
114+
const result = SearchRequest.findSearchRequestType(counterInput);
115+
expect(result).toBe(expectedType);
116+
});
117+
}
114118
});
115119

116120
});

src/search/test_cases/search_wildcards.test.e2e.ts

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,29 @@ describe('Wildcard Searches', () => {
1515

1616
describe('SearchRequest.tokenizeSearchText()', () => {
1717
const testCases: Array<{ input: string; expected: string[]; }> = [
18-
// Wildcards in 1 term
19-
{ input: `"microsoft"`, expected: ["microsoft"] },
20-
{ input: "127.0.0.*", expected: ["127.0.0.*"] },
21-
{ input: `"microsoft *"`, expected: ["microsoft *"] },
22-
// Wildcards in 2 terms
23-
{ input: `"micro* office"`, expected: ["micro* office"] },
24-
{ input: `"microsoft off*"`, expected: ["microsoft off*"] },
25-
{ input: `"*soft office"`, expected: ["*soft office"] },
26-
{ input: `"*soft off*"`, expected: ["*soft off*"] },
27-
{ input: `"CVE*" "micro*"`, expected: ["CVE*", "micro*"] },
28-
// Wildcards in 3 terms
29-
{ input: `"CVE*" "microsoft*" "off*"`, expected: ["CVE*", "microsoft*", "off*"] },
30-
// Wildcards in phrases
31-
{ input: `"microsoft off*"`, expected: ["microsoft off*"] },
32-
{ input: `"CVE*" "*soft office"`, expected: ["CVE*", "*soft office"] },
33-
];
34-
35-
testCases.forEach(({ input, expected }) => {
36-
it(`should correctly tokenize "${input}" into ${JSON.stringify(expected)}`, () => {
37-
const result = SearchRequest.tokenizeSearchText(input);
38-
expect(result).toEqual(expected);
18+
// Wildcards in 1 term
19+
{ input: "127.0.0.*", expected: ["127.0.0.*"] },
20+
{ input: `"microsoft *"`, expected: ["microsoft *"] },
21+
// Wildcards in 2 terms
22+
{ input: `"micro* office"`, expected: ["micro* office"] },
23+
{ input: `"microsoft off*"`, expected: ["microsoft off*"] },
24+
{ input: `"*soft office"`, expected: ["*soft office"] },
25+
{ input: `"*soft off*"`, expected: ["*soft off*"] },
26+
{ input: `"CVE*" "micro*"`, expected: ["CVE*", "micro*"] },
27+
// Wildcards in 3 terms
28+
{ input: `"CVE*" "microsoft*" "off*"`, expected: ["CVE*", "microsoft*", "off*"] },
29+
// Wildcards in phrases
30+
{ input: `"microsoft off*"`, expected: ["microsoft off*"] },
31+
{ input: `"CVE*" "*soft office"`, expected: ["CVE*", "*soft office"] },
32+
];
33+
34+
testCases.forEach(({ input, expected }) => {
35+
it(`should correctly tokenize "${input}" into ${JSON.stringify(expected)}`, () => {
36+
const result = SearchRequest.tokenizeSearchText(input);
37+
expect(result).toEqual(expected);
38+
});
3939
});
4040
});
41-
});
4241

4342

4443
describe('SearchRequest.findSearchRequestType()', () => {
@@ -130,6 +129,15 @@ describe('Wildcard Searches', () => {
130129
{ input: "m*cro*f*", succeed: true, expectedNum: 52 }, //@todo, expected 65, same as "micro*"
131130
{ input: "m**t", succeed: true, expectedNum: 237 }, //@todo, should return error due to repeating *
132131
{ input: "m*****t", succeed: true, expectedNum: 237 }, //@todo, should return error due to repeating *
132+
// @todo
133+
// [`"micro????"`, [`"micro????"`]], // @todo
134+
// [`micro*`, [`micro*`]],
135+
// [`*micro*`, [`*micro*`]],
136+
// [`*micro**`, [`*micro**`]],
137+
// [`*micro *`, [`*micro`, `*`]],
138+
// [`micro????`, [`micro????`]],
139+
// [`micro*????`, [`micro*????`]],
140+
133141
// ----- ????? -----
134142
// { input: ".127.0.0.*", expectedNum: 2 },
135143
// { input: ".127.0.0.???", expectedType: 'SEARCH_AS_WILDCARD_QUESTION', expectedProcessedText: ".127.0.0.???" }, //

0 commit comments

Comments
 (0)