[DELEGATION] Edit attendance and events, delete event for someone (#553)

* [#523] added way to manage attendance for delegated calendars

* [DELEGATION] #523 & #524 changed eventURL calculation for delegated events to have the rigth one

* [#524] added drag and drop authorisation to delegated event

* [#524] added tests

* [#524] support for the permission access

* [#524] no edit when delegated event is not public
This commit is contained in:
Camille Moussu
2026-02-19 09:50:29 +01:00
committed by GitHub
parent f520a9b6fe
commit cfb2b59584
52 changed files with 1116 additions and 219 deletions
+163 -1
View File
@@ -1,5 +1,11 @@
import { getCalendarVisibility } from "@/components/Calendar/utils/calendarUtils";
import {
eventToFullCalendarFormat,
getCalendarVisibility,
} from "@/components/Calendar/utils/calendarUtils";
import { Calendar, DelegationAccess } from "@/features/Calendars/CalendarTypes";
import { AclEntry } from "@/features/Calendars/types/CalendarData";
import { CalendarEvent } from "@/features/Events/EventsTypes";
import { userOrganiser } from "@/features/User/userDataTypes";
describe("getCalendarVisibility", () => {
it("returns 'public' when {DAV:}authenticated has {DAV:}read", () => {
@@ -67,3 +73,159 @@ describe("getCalendarVisibility", () => {
expect(getCalendarVisibility(acl)).toBe("public");
});
});
jest.mock("twake-i18n", () => ({
useI18n: () => ({ t: (key: string) => key }),
}));
const baseEvent: CalendarEvent = {
uid: "event-1",
calId: "user1/cal1",
title: "Test Event",
start: "2024-01-15T10:00:00",
end: "2024-01-15T11:00:00",
allday: false,
organizer: { cal_address: "owner@example.com" } as userOrganiser,
color: { light: "#FF0000" },
class: "PUBLIC",
} as unknown as CalendarEvent;
const makeCalendar = (overrides: Partial<Calendar> = {}): Calendar =>
({
id: "user1/cal1",
name: "Test Calendar",
delegated: false,
access: {} as DelegationAccess,
owner: { emails: ["alice@example.com"], firstname: "Alice" },
color: { light: "#FF0000" },
events: {},
...overrides,
}) as Calendar;
const callFormat = (
event: CalendarEvent,
calendars: Record<string, Calendar>,
userAddress = "alice@example.com",
userId = "user1"
) =>
eventToFullCalendarFormat([event], [], userId, userAddress, false, calendars);
describe("eventToFullCalendarFormat - editable flag", () => {
describe("personal calendar (non-delegated)", () => {
const calendar = makeCalendar({ delegated: false });
it("is editable when user is organizer", () => {
const event = {
...baseEvent,
organizer: { cal_address: "alice@example.com" },
} as CalendarEvent;
const [result] = callFormat(event, { "user1/cal1": calendar });
expect(result.editable).toBe(true);
});
it("is not editable when user is not organizer", () => {
const event = {
...baseEvent,
organizer: { cal_address: "other@example.com" },
} as CalendarEvent;
const [result] = callFormat(event, { "user1/cal1": calendar });
expect(result.editable).toBe(false);
});
it("is not editable when pending", () => {
const event = {
...baseEvent,
organizer: { cal_address: "alice@example.com" },
} as CalendarEvent;
const [result] = eventToFullCalendarFormat(
[event],
[],
"user1",
"alice@example.com",
true,
{ "user1/cal1": calendar }
);
expect(result.editable).toBe(false);
});
});
describe("delegated calendar", () => {
const writeDelegatedCalendar = makeCalendar({
id: "user2/cal1",
delegated: true,
access: { write: true } as DelegationAccess,
owner: { emails: ["owner@example.com"], firstname: "Owner" },
});
const readDelegatedCalendar = makeCalendar({
id: "user2/cal1",
delegated: true,
access: { read: true } as DelegationAccess,
owner: { emails: ["owner@example.com"], firstname: "Owner" },
});
const event = { ...baseEvent, calId: "user2/cal1" };
it("is editable when owner is organizer and delegated user has write rights", () => {
const [result] = callFormat(
{
...event,
organizer: { cal_address: "owner@example.com" },
} as CalendarEvent,
{ "user2/cal1": writeDelegatedCalendar }
);
expect(result.editable).toBe(true);
});
it("is not editable when owner is organizer but delegated user only has read access", () => {
const [result] = callFormat(
{
...event,
organizer: { cal_address: "owner@example.com" },
} as CalendarEvent,
{ "user2/cal1": readDelegatedCalendar }
);
expect(result.editable).toBe(false);
});
it("is not editable when owner is not organizer", () => {
const [result] = callFormat(
{
...event,
organizer: { cal_address: "someone-else@example.com" },
} as CalendarEvent,
{ "user2/cal1": writeDelegatedCalendar }
);
expect(result.editable).toBe(false);
});
it("is not editable when pending even if owner is organizer", () => {
const [result] = eventToFullCalendarFormat(
[
{
...event,
organizer: { cal_address: "owner@example.com" },
} as CalendarEvent,
],
[],
"user1",
"alice@example.com",
true,
{ "user2/cal1": writeDelegatedCalendar }
);
expect(result.editable).toBe(false);
});
it("uses owner email not logged-in user email for organizer check", () => {
// logged-in user is alice, owner is owner@example.com
// organizer matches owner → editable
const [result] = callFormat(
{
...event,
organizer: { cal_address: "owner@example.com" },
} as CalendarEvent,
{ "user2/cal1": writeDelegatedCalendar },
"alice@example.com" // logged-in user, should NOT be used for delegated
);
expect(result.editable).toBe(true);
});
});
});