feat: add timezone-aware TOTP time travel
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
formatDateTimeLocal,
|
||||
parseLocalDateTime,
|
||||
resolveZonedDateTime,
|
||||
shiftTimestampByPeriods,
|
||||
zonedDateTimeCandidates,
|
||||
} from "../../src/otp/time";
|
||||
|
||||
describe("TOTP time travel", () => {
|
||||
it("parses exact UTC wall-clock values", () => {
|
||||
const resolved = resolveZonedDateTime("2025-08-19T12:34:56", "UTC");
|
||||
expect(new Date(resolved.timestampMs).toISOString()).toBe(
|
||||
"2025-08-19T12:34:56.000Z",
|
||||
);
|
||||
expect(formatDateTimeLocal(resolved.timestampMs, "UTC")).toBe(
|
||||
"2025-08-19T12:34:56",
|
||||
);
|
||||
expect(
|
||||
resolveZonedDateTime("2025-08-19T12:34:56.125", "UTC").timestampMs,
|
||||
).toBe(Date.UTC(2025, 7, 19, 12, 34, 56, 125));
|
||||
});
|
||||
|
||||
it("respects seasonal IANA timezone offsets", () => {
|
||||
expect(
|
||||
new Date(
|
||||
resolveZonedDateTime("2024-01-15T12:00:00", "America/New_York")
|
||||
.timestampMs,
|
||||
).toISOString(),
|
||||
).toBe("2024-01-15T17:00:00.000Z");
|
||||
expect(
|
||||
new Date(
|
||||
resolveZonedDateTime("2024-07-15T12:00:00", "America/New_York")
|
||||
.timestampMs,
|
||||
).toISOString(),
|
||||
).toBe("2024-07-15T16:00:00.000Z");
|
||||
});
|
||||
|
||||
it("rejects nonexistent DST times and exposes both repeated times", () => {
|
||||
expect(() =>
|
||||
resolveZonedDateTime("2024-03-10T02:30:00", "America/New_York"),
|
||||
).toThrow(/does not exist/iu);
|
||||
|
||||
const candidates = zonedDateTimeCandidates(
|
||||
"2024-11-03T01:30:00",
|
||||
"America/New_York",
|
||||
);
|
||||
expect(candidates.map((value) => new Date(value).toISOString())).toEqual([
|
||||
"2024-11-03T05:30:00.000Z",
|
||||
"2024-11-03T06:30:00.000Z",
|
||||
]);
|
||||
expect(
|
||||
new Date(
|
||||
resolveZonedDateTime("2024-11-03T01:30:00", "America/New_York", "later")
|
||||
.timestampMs,
|
||||
).toISOString(),
|
||||
).toBe("2024-11-03T06:30:00.000Z");
|
||||
});
|
||||
|
||||
it("handles non-hour timezone offsets", () => {
|
||||
expect(
|
||||
new Date(
|
||||
resolveZonedDateTime("2025-08-19T12:00:00", "Asia/Kathmandu")
|
||||
.timestampMs,
|
||||
).toISOString(),
|
||||
).toBe("2025-08-19T06:15:00.000Z");
|
||||
});
|
||||
|
||||
it("validates calendar values, timezone names and bounded period shifts", () => {
|
||||
expect(() => parseLocalDateTime("2025-02-29T12:00:00")).toThrow(
|
||||
/valid calendar/iu,
|
||||
);
|
||||
expect(() =>
|
||||
resolveZonedDateTime("2025-01-01T00:00:00", "Mars/Base"),
|
||||
).toThrow(/timezone/iu);
|
||||
expect(shiftTimestampByPeriods(90_000, 30, -2)).toBe(30_000);
|
||||
expect(() => shiftTimestampByPeriods(1_000, 30, -1)).toThrow(
|
||||
/outside supported time/iu,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user