feat: add calendar event normalization and sanitization functions with tests
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { CalendarEvent } from '@/lib/jmap/types';
|
||||
import {
|
||||
isAllDayEventLike,
|
||||
normalizeCalendarEventLike,
|
||||
sanitizeOutgoingCalendarEventData,
|
||||
} from '../calendar-event-normalization';
|
||||
|
||||
function makeEvent(overrides: Partial<CalendarEvent> = {}): Partial<CalendarEvent> {
|
||||
return {
|
||||
start: '2026-03-16T00:00:00',
|
||||
duration: 'PT24H',
|
||||
showWithoutTime: false,
|
||||
timeZone: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('calendar event normalization', () => {
|
||||
it('infers all-day events from midnight-to-midnight spans without the flag', () => {
|
||||
expect(isAllDayEventLike(makeEvent())).toBe(true);
|
||||
});
|
||||
|
||||
it('does not infer all-day for midnight events with non-day durations', () => {
|
||||
expect(isAllDayEventLike(makeEvent({ duration: 'PT12H' }))).toBe(false);
|
||||
});
|
||||
|
||||
it('normalizes inferred all-day durations to day units', () => {
|
||||
expect(normalizeCalendarEventLike(makeEvent({ duration: 'PT48H' }))).toMatchObject({
|
||||
showWithoutTime: true,
|
||||
duration: 'P2D',
|
||||
});
|
||||
});
|
||||
|
||||
it('sanitizes outgoing all-day starts to date-only values', () => {
|
||||
expect(sanitizeOutgoingCalendarEventData(makeEvent({
|
||||
showWithoutTime: true,
|
||||
start: '2026-03-16T00:00:00',
|
||||
duration: 'PT24H',
|
||||
timeZone: 'UTC',
|
||||
}))).toMatchObject({
|
||||
start: '2026-03-16',
|
||||
duration: 'P1D',
|
||||
timeZone: null,
|
||||
showWithoutTime: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -372,6 +372,16 @@ describe('formatEventSummary', () => {
|
||||
expect(summary.isAllDay).toBe(true);
|
||||
});
|
||||
|
||||
it('infers all-day summaries from midnight-to-midnight events without the flag', () => {
|
||||
const summary = formatEventSummary({
|
||||
start: '2026-03-16T00:00:00',
|
||||
duration: 'PT24H',
|
||||
});
|
||||
expect(summary.start).toBe('2026-03-16T00:00:00');
|
||||
expect(summary.end).toBe('2026-03-17T00:00:00');
|
||||
expect(summary.isAllDay).toBe(true);
|
||||
});
|
||||
|
||||
it('returns local format end for timed events with local start', () => {
|
||||
const summary = formatEventSummary({
|
||||
start: '2026-02-17T10:00:00',
|
||||
|
||||
@@ -171,4 +171,43 @@ describe('sieve generator', () => {
|
||||
expect(script).toContain('"Line with \\"quotes\\" and \\\\backslash"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Stalwart vacation-only script detection (parseScript)', () => {
|
||||
it('should detect a plain Stalwart vacation-only script as non-opaque', () => {
|
||||
const script = `require ["vacation"];\n\nvacation :subject "OOO" "I am away.";\n`;
|
||||
const result = parseScript(script);
|
||||
expect(result.isOpaque).toBe(false);
|
||||
expect(result.vacation?.isEnabled).toBe(true);
|
||||
expect(result.vacation?.subject).toBe('OOO');
|
||||
expect(result.rules).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should detect vacation script when body contains "if" keyword', () => {
|
||||
const script = `require ["vacation"];\n\nvacation :subject "Away" "if you need help contact support.";\n`;
|
||||
const result = parseScript(script);
|
||||
expect(result.isOpaque).toBe(false);
|
||||
expect(result.vacation?.isEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect vacation script when body contains "else" keyword', () => {
|
||||
const script = `require ["vacation"];\n\nvacation "Otherwise I will respond when I return.";\n`;
|
||||
const result = parseScript(script);
|
||||
expect(result.isOpaque).toBe(false);
|
||||
expect(result.vacation?.isEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should mark as opaque when real filter rules exist alongside vacation', () => {
|
||||
const script = `require ["vacation", "fileinto"];\n\nvacation "Away";\n\nif header :contains "From" "boss@example.com" {\n fileinto "Important";\n}\n`;
|
||||
const result = parseScript(script);
|
||||
expect(result.isOpaque).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle Stalwart :mime format vacation script', () => {
|
||||
const script = `require ["vacation", "relational", "date"];\n\nvacation :mime :subject "Test" "Content-Type: text/plain; charset=\\"utf-8\\"\nContent-Transfer-Encoding: 7bit\n\ntest";\n`;
|
||||
const result = parseScript(script);
|
||||
expect(result.isOpaque).toBe(false);
|
||||
expect(result.vacation?.isEnabled).toBe(true);
|
||||
expect(result.vacation?.subject).toBe('Test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user