Files
openclaw/src/cron/parse.test.ts
SpecialLeon 94b710ac00 test(cron): expand parseAbsoluteTimeMs test coverage to 39 cases (#91656)
Summary:
- The PR expands `src/cron/parse.test.ts` with grouped `parseAbsoluteTimeMs` coverage for epoch, ISO timezone/offset, precision, whitespace, invalid-format, and cron example cases.
- PR surface: Tests +233. Total +233 across 1 file.
- Reproducibility: not applicable. this is a test coverage PR, not a runtime bug report with user steps. Source inspection confirms the requested parser coverage is still added only by this open PR path.

Automerge notes:
- Ran the ClawSweeper repair loop before final review.
- Included post-review commit in the final squash: test(cron): expand parseAbsoluteTimeMs test coverage to 39 cases

Validation:
- ClawSweeper review passed for head 69a49d9512.
- Required merge gates passed before the squash merge.

Prepared head SHA: 69a49d9512
Review: https://github.com/openclaw/openclaw/pull/91656#issuecomment-4657254372

Co-authored-by: 刘江0668001123 <liu.jiang2@xydigit.com>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
2026-06-19 06:36:31 +00:00

270 lines
11 KiB
TypeScript

// Cron parse tests cover CLI and config parsing for scheduled jobs.
import { describe, expect, it } from "vitest";
import { parseAbsoluteTimeMs } from "./parse.js";
describe("parseAbsoluteTimeMs", () => {
describe("epoch milliseconds", () => {
it("parses positive epoch milliseconds", () => {
expect(parseAbsoluteTimeMs("1700000000000")).toBe(1_700_000_000_000);
});
it("rejects digit-only timestamps outside the Date range", () => {
expect(parseAbsoluteTimeMs(String(Number.MAX_SAFE_INTEGER))).toBeNull();
});
it("rejects negative epoch milliseconds", () => {
// Negative numbers don't match /^\d+$/ pattern, so they're parsed as dates
// "-1000" is interpreted as a date string by Date.parse()
// This tests that very old timestamps outside valid range are rejected
expect(parseAbsoluteTimeMs("-8640000000000001")).toBeNull();
});
it("rejects non-numeric strings that look like numbers", () => {
expect(parseAbsoluteTimeMs("123abc")).toBeNull();
});
});
describe("ISO 8601 date only", () => {
it("parses date only as midnight UTC", () => {
expect(parseAbsoluteTimeMs("2024-01-15")).toBe(Date.parse("2024-01-15T00:00:00Z"));
});
it("parses date with implicit Z suffix", () => {
expect(parseAbsoluteTimeMs("2024-06-01")).toBe(Date.parse("2024-06-01T00:00:00Z"));
});
});
describe("ISO 8601 datetime without timezone", () => {
it("parses datetime without timezone as UTC", () => {
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00")).toBe(Date.parse("2024-01-15T10:30:00Z"));
});
it("parses datetime with seconds as UTC", () => {
expect(parseAbsoluteTimeMs("2024-03-20T15:45:30")).toBe(Date.parse("2024-03-20T15:45:30Z"));
});
});
describe("ISO 8601 with Z (UTC) timezone", () => {
it("parses datetime with Z suffix", () => {
const expected = Date.parse("2024-01-15T10:30:00Z");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00Z")).toBe(expected);
});
it("parses datetime with lowercase z suffix", () => {
const expected = Date.parse("2024-01-15T10:30:00Z");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00z")).toBe(expected);
});
it("parses datetime with milliseconds and Z", () => {
const expected = Date.parse("2024-01-15T10:30:45.123Z");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.123Z")).toBe(expected);
});
it("parses datetime with microseconds and Z", () => {
// JavaScript Date has millisecond precision, but should parse without error
const expected = Date.parse("2024-01-15T10:30:45.123456Z");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.123456Z")).toBe(expected);
});
it("parses datetime with nanoseconds and Z (truncates to ms)", () => {
const expected = Date.parse("2024-01-15T10:30:45.123456789Z");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.123456789Z")).toBe(expected);
});
});
describe("ISO 8601 with timezone offset (colon format)", () => {
it("parses datetime with positive offset +HH:MM", () => {
// UTC+8 (Beijing/Singapore)
const expected = Date.parse("2024-01-15T10:30:00+08:00");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+08:00")).toBe(expected);
});
it("parses datetime with negative offset -HH:MM", () => {
// UTC-5 (EST)
const expected = Date.parse("2024-01-15T10:30:00-05:00");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00-05:00")).toBe(expected);
});
it("parses datetime with zero offset +00:00", () => {
const expected = Date.parse("2024-01-15T10:30:00+00:00");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+00:00")).toBe(expected);
});
it("parses datetime with maximum positive offset +14:00", () => {
const expected = Date.parse("2024-01-15T10:30:00+14:00");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+14:00")).toBe(expected);
});
it("parses datetime with maximum negative offset -12:00", () => {
const expected = Date.parse("2024-01-15T10:30:00-12:00");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00-12:00")).toBe(expected);
});
it("parses datetime with half-hour offset +05:30", () => {
// India Standard Time
const expected = Date.parse("2024-01-15T10:30:00+05:30");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+05:30")).toBe(expected);
});
it("parses datetime with 45-minute offset +12:45", () => {
// New Zealand Chatham Islands
const expected = Date.parse("2024-01-15T10:30:00+12:45");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+12:45")).toBe(expected);
});
});
describe("ISO 8601 with timezone offset (no colon format)", () => {
it("parses datetime with positive offset +HHMM", () => {
const expected = Date.parse("2024-01-15T10:30:00+0800");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+0800")).toBe(expected);
});
it("parses datetime with negative offset -HHMM", () => {
const expected = Date.parse("2024-01-15T10:30:00-0500");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00-0500")).toBe(expected);
});
it("parses datetime with zero offset +0000", () => {
const expected = Date.parse("2024-01-15T10:30:00+0000");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+0000")).toBe(expected);
});
});
describe("ISO 8601 with milliseconds and timezone", () => {
it("parses datetime with milliseconds and positive offset", () => {
const expected = Date.parse("2024-01-15T10:30:45.500+08:00");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.500+08:00")).toBe(expected);
});
it("parses datetime with milliseconds and negative offset", () => {
const expected = Date.parse("2024-01-15T10:30:45.999-05:00");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.999-05:00")).toBe(expected);
});
it("parses datetime with microseconds and timezone", () => {
const expected = Date.parse("2024-01-15T10:30:45.123456+08:00");
expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.123456+08:00")).toBe(expected);
});
});
describe("whitespace handling", () => {
it("trims leading and trailing whitespace", () => {
expect(parseAbsoluteTimeMs(" 1700000000000 ")).toBe(1_700_000_000_000);
expect(parseAbsoluteTimeMs(" 2024-01-15T10:30:00Z ")).toBe(
Date.parse("2024-01-15T10:30:00Z"),
);
});
it("rejects strings with only whitespace", () => {
expect(parseAbsoluteTimeMs("")).toBeNull();
expect(parseAbsoluteTimeMs(" ")).toBeNull();
});
});
describe("invalid formats", () => {
it("rejects invalid date strings", () => {
expect(parseAbsoluteTimeMs("not-a-date")).toBeNull();
expect(parseAbsoluteTimeMs("2024-13-40")).toBeNull();
expect(parseAbsoluteTimeMs("invalid")).toBeNull();
});
it("rejects truly malformed date strings", () => {
// JavaScript Date.parse is very lenient, so we test only truly invalid formats
expect(parseAbsoluteTimeMs("24-01-15")).toBeNull(); // Two-digit year too ambiguous
expect(parseAbsoluteTimeMs("not-a-date")).toBeNull();
expect(parseAbsoluteTimeMs("")).toBeNull();
});
it("rejects non-padded ISO-like date formats", () => {
expect(parseAbsoluteTimeMs("2024-1-15")).toBeNull();
});
it("rejects incomplete datetime strings", () => {
expect(parseAbsoluteTimeMs("2024-01-15T")).toBeNull();
expect(parseAbsoluteTimeMs("2024-01-15T10")).toBeNull();
});
it("rejects invalid timezone formats", () => {
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+8:00")).toBeNull();
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00+080")).toBeNull();
expect(parseAbsoluteTimeMs("2024-01-15T10:30:00GMT")).toBeNull();
});
});
describe("edge cases", () => {
it("handles leap year dates", () => {
expect(parseAbsoluteTimeMs("2024-02-29T00:00:00Z")).toBe(Date.parse("2024-02-29T00:00:00Z"));
});
it("handles year boundary dates", () => {
expect(parseAbsoluteTimeMs("2023-12-31T23:59:59Z")).toBe(Date.parse("2023-12-31T23:59:59Z"));
expect(parseAbsoluteTimeMs("2024-01-01T00:00:00Z")).toBe(Date.parse("2024-01-01T00:00:00Z"));
});
it("handles edge of valid timestamp range", () => {
// JavaScript Date range is approximately -100,000,000 to +100,000,000 days
// Test timestamps well within the valid range that are realistic for cron usage
const year2000 = new Date("2000-01-01T00:00:00Z").getTime();
expect(parseAbsoluteTimeMs(year2000.toString())).toBe(year2000);
const year2050 = new Date("2050-01-01T00:00:00Z").getTime();
expect(parseAbsoluteTimeMs(year2050.toString())).toBe(year2050);
});
it("handles maximum valid timestamp", () => {
// JavaScript Date range ends at +100,000,000 days
const maxValid = new Date(8640000000000000).getTime();
expect(parseAbsoluteTimeMs(maxValid.toString())).toBe(maxValid);
});
});
describe("real-world cron examples", () => {
it("parses common cron scheduling timestamps", () => {
// Daily at midnight UTC
expect(parseAbsoluteTimeMs("2024-01-01T00:00:00Z")).toBe(Date.parse("2024-01-01T00:00:00Z"));
// Hourly at the top of the hour
expect(parseAbsoluteTimeMs("2024-06-15T12:00:00+00:00")).toBe(
Date.parse("2024-06-15T12:00:00+00:00"),
);
// Specific business hours in different timezones
expect(parseAbsoluteTimeMs("2024-03-01T09:00:00+08:00")).toBe(
Date.parse("2024-03-01T09:00:00+08:00"),
);
});
it("parses timestamps with sub-second precision for high-frequency jobs", () => {
expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.001Z")).toBe(
Date.parse("2024-01-15T10:30:45.001Z"),
);
expect(parseAbsoluteTimeMs("2024-01-15T10:30:45.999Z")).toBe(
Date.parse("2024-01-15T10:30:45.999Z"),
);
});
});
it("parses ISO timestamps with UTC defaults and explicit offsets", () => {
expect(parseAbsoluteTimeMs("2026-02-28")).toBe(Date.parse("2026-02-28T00:00:00Z"));
expect(parseAbsoluteTimeMs("2026-02-28T12:34:56.789Z")).toBe(
Date.parse("2026-02-28T12:34:56.789Z"),
);
expect(parseAbsoluteTimeMs("2026-02-28T24:00:00Z")).toBe(Date.parse("2026-02-28T24:00:00Z"));
expect(parseAbsoluteTimeMs("2026-02-28T12:34:56+08:00")).toBe(
Date.parse("2026-02-28T12:34:56+08:00"),
);
});
it.each([
"2023-02-29",
"2026-02-31",
"2026-02-31T00:00:00Z",
"2026-04-31T12:34:56Z",
"2026-01-01T25:00:00Z",
"December 17, 2026 03:24:00",
"2026/12/17",
])("rejects invalid absolute timestamp %s", (input) => {
expect(parseAbsoluteTimeMs(input)).toBeNull();
});
});