diff --git a/apps/dav/src/service/CalendarService.spec.ts b/apps/dav/src/service/CalendarService.spec.ts new file mode 100644 index 0000000000000..f41f362771989 --- /dev/null +++ b/apps/dav/src/service/CalendarService.spec.ts @@ -0,0 +1,48 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { afterEach, describe, expect, it, vi } from 'vitest' +import { getEmptySlots, saveScheduleInboxAvailability } from './CalendarService.ts' + +const customRequest = vi.fn() +vi.mock('../dav/client.ts', () => ({ + getClient: () => ({ customRequest }), +})) + +afterEach(() => { + customRequest.mockReset() +}) + +describe('saveScheduleInboxAvailability', () => { + it('removes the availability property when there are no slots', async () => { + await saveScheduleInboxAvailability(getEmptySlots(), 'UTC') + + expect(customRequest).toHaveBeenCalledOnce() + const [path, { method, data }] = customRequest.mock.calls[0] + expect(path).toBe('inbox') + expect(method).toBe('PROPPATCH') + expect(data).toContain('') + expect(data).not.toContain('') + expect(data).not.toContain('VAVAILABILITY') + }) + + it('sets the availability property when there are slots', async () => { + const slots = getEmptySlots() + slots.MO = [{ + start: new Date('2026-10-12T09:00:00Z').getTime() / 1000, + end: new Date('2026-10-12T17:00:00Z').getTime() / 1000, + }] + + await saveScheduleInboxAvailability(slots, 'UTC') + + expect(customRequest).toHaveBeenCalledOnce() + const [path, { method, data }] = customRequest.mock.calls[0] + expect(path).toBe('inbox') + expect(method).toBe('PROPPATCH') + expect(data).toContain('') + expect(data).toContain('BEGIN:VAVAILABILITY') + expect(data).toContain('BEGIN:AVAILABLE') + }) +}) diff --git a/apps/dav/src/service/CalendarService.ts b/apps/dav/src/service/CalendarService.ts index 2a60e87f44cec..17613d233430c 100644 --- a/apps/dav/src/service/CalendarService.ts +++ b/apps/dav/src/service/CalendarService.ts @@ -66,6 +66,24 @@ export async function saveScheduleInboxAvailability(slots, timezoneId) { day: dayId, })))] + // An empty VAVAILABILITY means unavailable at all times (RFC 7953), so remove the property instead + if (all.length === 0) { + logger.debug('No availability slots, removing availability') + + await getClient().customRequest('inbox', { + method: 'PROPPATCH', + data: ` + + + + + + + `, + }) + return + } + const vavailability = slotsToVavailability(all, timezoneId) logger.debug('New availability ical created', {