Skip to content

Commit b1cdcf0

Browse files
committed
Merge remote-tracking branch 'origin/dev' into hk/006a_exact_phrase_search
2 parents 26d1a75 + 9605562 commit b1cdcf0

31 files changed

+2537
-317
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Notes.md
4949
.env.*
5050

5151
# development and testing files
52+
.notes
5253
aws.temp
5354
bulk/converted*.jsonl
5455
cves/deltaLog.json

ChangeLog.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
# Change Log
22

3-
### 2.2.0-rc1 (Sprint 1)
3+
### 2.2.0-rc3 (Sprint 4)
44
- exact phrase search using double quotes
5+
- date and date range search on date fields, using the following formats (date ranges are inclusive):
6+
- YYYY-MM-DD
7+
- YYYY-MM-DDTHH:MM:SS(.mmm)(Z) (where the .mmm and Z are optional, defaults to .000Z if missing)
8+
- YYYY-MM-DD..YYYY-MM-DD
9+
- YYYY-MM-DDTHH:MM:SS(.mmm)(Z)..YYYY-MM-DDTHH:MM:SS.(mmm)(Z)
510

611
### 2.1.0
712
- wildcard search using "*" and "?"

config/devel.jsonc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
"fixtures": {
3232
// @todo these constants needs to be in sync in cve-fixtures
3333
// so that testing snapshots are consistent and valid
34-
"name": "fixtures-search-baseline-1086", // release tag
35-
"numCves": "1086" // possible identifier assuming we always add cves to a new release
34+
"name": "fixtures-search-baseline-1008", // release tag
35+
"numCves": "1008" // possible identifier assuming we always add cves to a new release
3636
}
3737
},
3838
// constants for testing node-config

index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export * from "./src/commands/GenericCommand.js";
2626
export * from "./src/commands/MainCommands.js";
2727

2828
// common
29+
export * from "./src/common/IsoDate/IsoDate.js";
30+
export * from "./src/common/IsoDate/IsoDatetime.js";
31+
export * from "./src/common/IsoDate/IsoDatetimeRange.js";
2932
export * from "./src/common/IsoDate/IsoDateString.js";
3033
export * from "./src/common/Json/Json.js";
3134
export * from "./src/common/comparer/ObjectComparer.js";

package-lock.json

Lines changed: 251 additions & 236 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cve-core",
3-
"version": "2.2.0-rc1",
3+
"version": "2.2.0-rc3",
44
"description": "CVE npm package for working with CVEs",
55
"type": "module",
66
"engines": {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { describe, test, expect } from '@jest/globals';
2+
import { toSearchDateDslString } from './OpensearchDatetimeUtils';
3+
import { IsoDate, IsoDatetime } from '../../common/IsoDate/IsoDatetime';
4+
5+
6+
describe('OpensearchDatetimeUtils.toSearchDateDslString()', () => {
7+
const testcases = [
8+
{ input: '2025-03-01T12:34:56.001Z', expected: '2025-03-01T12:34:56.001Z' },
9+
{ input: '2025-03-01T12:34:56.001', expected: '2025-03-01T12:34:56.001Z' },
10+
{ input: '2025-03-01T12:34:56Z', expected: '2025-03-01T12:34:56Z' },
11+
{ input: '2025-03-01T12:34:56', expected: '2025-03-01T12:34:56Z' },
12+
{ input: '2025-03-01', expected: '2025-03-01||/d' },
13+
{ input: '2025-03', expected: '2025-03||/M' },
14+
{ input: '2025', expected: '2025||/y' },
15+
];
16+
17+
testcases.forEach(({ input, expected }) => {
18+
test(`throws for "${input}"`, () => {
19+
let iso = (input.length > 10) ? IsoDatetime.parse(input) : IsoDate.parse(input);
20+
expect(toSearchDateDslString(iso)).toBe(expected);
21+
});
22+
});
23+
24+
test('throws on invalid date string', () => {
25+
expect(() => IsoDate.parse('2025-13')).toThrow();
26+
});
27+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* OpenSearch and ElasticSearch both use additional symbols in the Search DSL to make ISO 8601 representations
3+
* even more precise for years, months, days. This utility class simplifies that
4+
*/
5+
6+
import { IsoDate, IsoDatetime } from '../../common/IsoDate/IsoDatetime.js';
7+
8+
export const toSearchDateDslString = (iso: IsoDate | IsoDatetime): string => {
9+
if (iso.isMonth()) {
10+
// isoDate is a month
11+
return `${iso.toString()}||/M`;
12+
}
13+
else if (iso.isYear()) {
14+
// isoDate is a year
15+
return `${iso.toString()}||/y`;
16+
}
17+
else if (iso.isDate()) {
18+
// isoDate is a year
19+
return `${iso.toString()}||/d`;
20+
}
21+
else {
22+
// all other instances will be the original string
23+
return iso.toString();
24+
}
25+
};
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/**
2+
* Unit tests for IsoDate
3+
*
4+
* AI Usage:
5+
* - This code was originally generated using
6+
* 1. OpenAI/GPT OSS 120b on Roo Code
7+
* 2. Gemini 2.5 Flash and 2.5 Pro
8+
* then modified to fix incorrect implementations and fit project needs.
9+
* The first commit contains these corrections so that all code committed
10+
* works as designed.
11+
*/
12+
13+
import { describe, test, expect } from '@jest/globals';
14+
import { IsoDate, isValidIsoDate } from './IsoDate';
15+
16+
describe('IsoDate.parse – valid inputs', () => {
17+
18+
test('full date with hyphens', () => {
19+
const str = '2025-01-01';
20+
expect(isValidIsoDate(str)).toBeTruthy();
21+
const d = IsoDate.parse(str);
22+
expect(d.year).toBe(2025);
23+
expect(d.month).toBe(1);
24+
expect(d.day).toBe(1);
25+
expect(d.toString()).toBe('2025-01-01');
26+
expect(d.isDate()).toBeTruthy();
27+
expect(d.isMonth()).toBeFalsy();
28+
expect(d.isYear()).toBeFalsy()
29+
});
30+
31+
test('year‑month', () => {
32+
const str = '2025-01';
33+
expect(isValidIsoDate(str)).toBeTruthy();
34+
const d = IsoDate.parse(str);
35+
expect(d.year).toBe(2025);
36+
expect(d.month).toBe(1);
37+
expect(d.day).toBeUndefined();
38+
expect(d.toString()).toBe('2025-01');
39+
expect(d.isDate()).toBeFalsy();
40+
expect(d.isMonth()).toBeTruthy();
41+
expect(d.isYear()).toBeFalsy()
42+
});
43+
44+
test('year only', () => {
45+
const str = '2025';
46+
expect(isValidIsoDate(str)).toBeTruthy();
47+
const d = IsoDate.parse(str);
48+
expect(d.year).toBe(2025);
49+
expect(d.month).toBeUndefined();
50+
expect(d.day).toBeUndefined();
51+
expect(d.toString()).toBe('2025');
52+
expect(d.isDate()).toBeFalsy();
53+
expect(d.isMonth()).toBeFalsy();
54+
expect(d.isYear()).toBeTruthy()
55+
});
56+
57+
test('leap‑year February 29', () => {
58+
const str = '2024-02-29';
59+
expect(isValidIsoDate(str)).toBeTruthy();
60+
const d = IsoDate.parse(str);
61+
expect(d.year).toBe(2024);
62+
expect(d.month).toBe(2);
63+
expect(d.day).toBe(29);
64+
expect(d.toString()).toBe('2024-02-29');
65+
});
66+
67+
test('month‑day boundary', () => {
68+
const str = '2025-01-30';
69+
expect(isValidIsoDate(str)).toBeTruthy();
70+
const d = IsoDate.parse(str);
71+
expect(d.year).toBe(2025);
72+
expect(d.month).toBe(1);
73+
expect(d.day).toBe(30);
74+
expect(d.toString()).toBe('2025-01-30');
75+
});
76+
});
77+
78+
79+
describe('IsoDate.parse/toString/isMonth/isDate/isYear', () => {
80+
const tests: Array<{ value: string; isYear?: boolean; isMonth?: boolean; isDate?: boolean; isDatetime?: boolean; threw?: RegExp; }> = [
81+
// valid ISO Date specs
82+
{ value: `2025`, isYear: true },
83+
{ value: ` 2025 `, isYear: true },
84+
{ value: `2025-10`, isMonth: true },
85+
{ value: `2024-02-29`, isDate: true },
86+
// invalid ISO Date specs
87+
{ value: `25`, threw: /Invalid calendar date format/ }, // year must be after 1000 and before 2500
88+
{ value: `1899`, threw: /Year out of range/ }, // year must be after 1900 and before 2100
89+
{ value: `2500`, threw: /Year out of range/ }, // year must be after 1900 and before 2100
90+
{ value: `1/1/25`, threw: /Invalid calendar date format/ }, // bad format
91+
{ value: `abc`, threw: /Invalid calendar date format/ }, // bad format
92+
{ value: `202501`, threw: /Invalid calendar date format/ }, // year+month without hyphen
93+
{ value: `20250101`, threw: /Invalid calendar date format/ }, // compact full date (properly rejected in this class)
94+
{ value: `250101`, threw: /Invalid calendar date format/ }, // two‑digit year
95+
{ value: `-2025-04-31`, threw: /Invalid calendar date format/ }, // leading hyphen
96+
{ value: `-2025-04`, threw: /Invalid calendar date format/ }, // leading hyphen
97+
{ value: `01-01`, threw: /Invalid calendar date format/ },
98+
{ value: `2025--01`, threw: /Invalid calendar date format/ },
99+
{ value: `2025-01-01T014:00:00:00Z`, threw: /Invalid calendar date format/ },
100+
{ value: `2025-13-01`, threw: /Month out of range/ },
101+
{ value: `2025-02-29`, threw: /Day out of range/ }, // not a leap year
102+
{ value: `2025-02-30`, threw: /Day out of range/ },
103+
{ value: `2025-04-31`, threw: /Day out of range/ },
104+
];
105+
106+
tests.forEach(({ value, isYear = false, isMonth = false, isDate = false, threw = '' }) => {
107+
test(`properly determines if ${value} is a ${isYear ? 'year' : ''}${isMonth ? 'month' : ''}${isDate ? 'date' : ''}${threw ? 'error' : ''}`, () => {
108+
if (threw) {
109+
expect(() => IsoDate.parse(value)).toThrow(threw);
110+
}
111+
else {
112+
const isoDate = IsoDate.parse(value);
113+
if (isYear) {
114+
expect(isoDate.isYear()).toBeTruthy();
115+
expect(isoDate.isMonth()).toBeFalsy();
116+
expect(isoDate.isDate()).toBeFalsy();
117+
expect(isoDate.isDatetime()).toBeFalsy();
118+
}
119+
else if (isMonth) {
120+
expect(isoDate.isYear()).toBeFalsy();
121+
expect(isoDate.isMonth()).toBeTruthy();
122+
expect(isoDate.isDate()).toBeFalsy();
123+
expect(isoDate.isDatetime()).toBeFalsy();
124+
}
125+
else if (isDate) {
126+
expect(isoDate.isYear()).toBeFalsy();
127+
expect(isoDate.isMonth()).toBeFalsy();
128+
expect(isoDate.isDate()).toBeTruthy();
129+
expect(isoDate.isDatetime()).toBeFalsy();
130+
}
131+
expect(isoDate.toString()).toBe(value.trim());
132+
}
133+
});
134+
});
135+
});
136+
137+
138+
describe('IsoDate.daysInMonth()', () => {
139+
const tests: Array<{ year: number; month: number, expected: number; }> = [
140+
{ year: 2025, month: 1, expected: 31 },
141+
{ year: 2025, month: 2, expected: 28 },
142+
{ year: 2025, month: 3, expected: 31 },
143+
{ year: 2025, month: 4, expected: 30 },
144+
{ year: 2025, month: 5, expected: 31 },
145+
{ year: 2025, month: 6, expected: 30 },
146+
{ year: 2025, month: 7, expected: 31 },
147+
{ year: 2025, month: 8, expected: 31 },
148+
{ year: 2025, month: 9, expected: 30 },
149+
{ year: 2025, month: 10, expected: 31 },
150+
{ year: 2025, month: 11, expected: 30 },
151+
{ year: 2025, month: 12, expected: 31 },
152+
{ year: 2024, month: 2, expected: 29 }, // leap year
153+
];
154+
155+
tests.forEach(({ year, month, expected }) => {
156+
test(`properly calculates ${year}-${month} to have ${expected} days`, () => {
157+
const days = IsoDate.daysInMonth(year, month);
158+
expect(days).toBe(expected);
159+
});
160+
});
161+
});

0 commit comments

Comments
 (0)