WIP: Figuring out tests for CalendarModel

This commit is contained in:
and 2025-12-05 16:51:02 +01:00
parent 808250f189
commit ebee33883f
5 changed files with 171 additions and 200 deletions

View file

@ -319,11 +319,6 @@ export class CalendarModel {
newEvent.startTime.getTime() !== existingEvent.startTime.getTime() ||
(await didLongStateChange(newEvent, existingEvent, zone))
) {
// FIXME come back when deciding about strategies and etc
// if (this.isDefaultCalendar(groupRoot._id) && isSameId(groupRoot.pendingEvents?.list ?? null, listIdPart(existingEvent._id))) {
// await this.createPendingEvent(newEvent, groupRoot, newAlarms, existingEvent)
// }
await this.doCreate(newEvent, zone, groupRoot, newAlarms, existingEvent)
// We should reload the instance here because session key and permissions are updated when we recreate event.
@ -687,7 +682,6 @@ export class CalendarModel {
const calendarInfos = await this.loadCalendarInfos(progressMonitor)
const firstPrivateCalendar = findFirstPrivateCalendar(calendarInfos)
const userSettingsGroupRoot = this.logins.getUserController().userSettingsGroupRoot
const isInternalUser = this.logins.isInternalUserLoggedIn()
if (!isInternalUser || firstPrivateCalendar) {
@ -746,29 +740,12 @@ export class CalendarModel {
// Reset permissions because server will assign them
downcast(event)._permissions = null
event._ownerGroup = groupRoot._id
event.pendingInvitation = existingEvent ? existingEvent.pendingInvitation : null
return await this.calendarFacade.saveCalendarEvent(event, alarmInfos, existingEvent ?? null).then(this.requestWidgetRefresh)
}
private async createPendingEvent(event: CalendarEvent, groupRoot: CalendarGroupRoot, existingEvent: CalendarEvent | null = null): Promise<void> {
// If the event was copied it might still carry some fields for re-encryption. We can't reuse them.
removeTechnicalFields(event)
const { assignEventId } = await import("../../../common/calendar/date/CalendarUtils")
assignEventId(event, getTimeZone(), groupRoot)
// Reset ownerEncSessionKey because it cannot be set for new entity, it will be assigned by the CryptoFacade
event._ownerEncSessionKey = null
if (event.repeatRule != null) {
event.repeatRule.excludedDates = event.repeatRule.excludedDates.map(({ date }) => createDateWrapper({ date }))
}
// Reset permissions because server will assign them
downcast(event)._permissions = null
event._ownerGroup = groupRoot._id
return await this.calendarFacade.saveCalendarEvent(event, [], existingEvent)
}
async deleteEvent(event: CalendarEvent): Promise<void> {
return await this.entityClient.erase(event).then(this.requestWidgetRefresh)
}
@ -973,7 +950,7 @@ export class CalendarModel {
if (dbEvents == null) {
// Create pending events when processing calendar invites.
const calendarInfos = await this.loadOrCreateCalendarInfo(this.readProgressMonitor.next().value)
const calendarInfos = await this.getCalendarInfos()
const firstCalendar = findFirstPrivateCalendar(calendarInfos)
if (firstCalendar == null) {
throw new Error("Missing private calendar")
@ -1004,9 +981,10 @@ export class CalendarModel {
const calendarEvent: CalendarEvent = {
...parsed.event,
sender,
pendingInvitation: true,
}
return this.createPendingEvent(calendarEvent, destinationCalendarGroupRoot)
return this.doCreate(calendarEvent, this.zone, destinationCalendarGroupRoot, [])
})
await Promise.all(eventsPromises)

View file

@ -155,6 +155,7 @@ export class CalendarFacade {
flags: {
hasAlarms: hasAlarmsForTheUser(this.userFacade.getLoggedInUser(), e),
isAlteredInstance: e.recurrenceId != null,
isGhost: !!e.pendingInvitation,
},
color,
}))
@ -163,6 +164,7 @@ export class CalendarFacade {
flags: {
hasAlarms: hasAlarmsForTheUser(this.userFacade.getLoggedInUser(), e),
isAlteredInstance: e.recurrenceId != null,
isGhost: !!e.pendingInvitation,
},
color,
}))

View file

@ -10,12 +10,7 @@ import Stream from "mithril/stream"
import stream from "mithril/stream"
import { EntityUpdateData } from "../../../src/common/api/common/utils/EntityUpdateUtils"
import { EndType, OperationType, RepeatPeriod } from "../../../src/common/api/common/TutanotaConstants"
import {
CalendarEventsRefTypeRef,
CalendarEventTypeRef,
CalendarGroupRootTypeRef,
CalendarRepeatRuleTypeRef,
} from "../../../src/common/api/entities/tutanota/TypeRefs"
import { CalendarEventTypeRef, CalendarGroupRootTypeRef, CalendarRepeatRuleTypeRef } from "../../../src/common/api/entities/tutanota/TypeRefs"
import { EntityClient } from "../../../src/common/api/common/EntityClient"
import { createTestEntity } from "../TestUtils"
import { CalendarFacade } from "../../../src/common/api/worker/facades/lazy/CalendarFacade"
@ -28,7 +23,6 @@ o.spec("CalendarEventRepositoryTest", function () {
const initialCalendarGroupId = "initialCalendarGroupId"
const userGroupId = "userGroupId"
const shotEventsListId = "shotEventsListId"
const pendingEventsListId = "pendingEventsListId"
const timezone = getTimeZone()
const eventControllerMock: EventController = object()
@ -39,18 +33,20 @@ o.spec("CalendarEventRepositoryTest", function () {
o.spec("createOrUpdateCalendarEvent", function () {
let userControllerMock: UserController
let calendarFacade: CalendarFacade
let calendarFacadeMock: CalendarFacade
let loginControllerMock: LoginController
let calendarModelMock: CalendarModel
let entityClientMock: EntityClient
let calendarInfosStreamMock: Stream<ReadonlyMap<Id, CalendarInfo>>
let calendarEventsRepositoryMock: CalendarEventsRepository
let eventsRepository: CalendarEventsRepository
let initialCalendarInfos: Map<string, CalendarInfo>
let initialCalendarMembership: GroupMembership
o.beforeEach(function () {
userControllerMock = object()
calendarFacade = object()
calendarFacadeMock = object()
loginControllerMock = object()
calendarModelMock = object()
entityClientMock = object()
@ -72,14 +68,13 @@ o.spec("CalendarEventRepositoryTest", function () {
const calendarInfo: CalendarInfo = object()
calendarInfo.groupRoot = createTestEntity(CalendarGroupRootTypeRef, {
shortEvents: shotEventsListId,
pendingEvents: createTestEntity(CalendarEventsRefTypeRef, { list: pendingEventsListId }),
})
initialCalendarInfos = new Map([[initialCalendarGroupId, calendarInfo]])
when(calendarModelMock.getCalendarInfos()).thenResolve(initialCalendarInfos)
calendarEventsRepositoryMock = new CalendarEventsRepository(
eventsRepository = new CalendarEventsRepository(
calendarModelMock,
calendarFacade,
calendarFacadeMock,
timezone,
entityClientMock,
eventControllerMock,
@ -104,11 +99,11 @@ o.spec("CalendarEventRepositoryTest", function () {
const dateFarFromEvent = new Date(2025, 11, 13)
const startOfDay = getStartOfDay(dateFarFromEvent).getTime()
const daysToEventsMock: DaysToEvents = new Map([[startOfDay, []]])
when(
calendarFacade.updateEventMap(matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything()),
).thenResolve(daysToEventsMock)
when(calendarFacadeMock.updateEventMap(matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything())).thenResolve(
daysToEventsMock,
)
// Making sure EventRepository.daysToEvents and EventRepository.loadedMonths is initialized
await calendarEventsRepositoryMock.loadMonthsIfNeeded([dateFarFromEvent], stream(false), null)
await eventsRepository.loadMonthsIfNeeded([dateFarFromEvent], stream(false), null)
// Act
const calendarEventUpdate: EntityUpdateData = object()
@ -119,7 +114,7 @@ o.spec("CalendarEventRepositoryTest", function () {
// Assert
const eventStartOfDay = getStartOfDay(eventStartDate).getTime()
const daysToEvents = calendarEventsRepositoryMock.getEventsForMonths()()
const daysToEvents = eventsRepository.getEventsForMonths()()
// We expect only the initial dateFarFromEvent to be loaded
o.check(daysToEvents.size).equals(1)
// Calling entityEventsListener should not add the event since the previously loaded day is in another month
@ -141,11 +136,11 @@ o.spec("CalendarEventRepositoryTest", function () {
const startOfDay = getStartOfDay(eventStartDate).getTime()
const daysToEventsMock: DaysToEvents = new Map([[startOfDay, []]])
when(
calendarFacade.updateEventMap(matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything()),
).thenResolve(daysToEventsMock)
when(calendarFacadeMock.updateEventMap(matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything())).thenResolve(
daysToEventsMock,
)
// Making sure EventRepository.daysToEvents and EventRepository.loadedMonths is initialized
await calendarEventsRepositoryMock.loadMonthsIfNeeded([eventStartDate], stream(false), null)
await eventsRepository.loadMonthsIfNeeded([eventStartDate], stream(false), null)
// Act
const calendarEventUpdate: EntityUpdateData = object()
@ -155,7 +150,7 @@ o.spec("CalendarEventRepositoryTest", function () {
await entityEventsListener!(updates, initialCalendarGroupId)
// Assert
const daysToEvents = calendarEventsRepositoryMock.getEventsForMonths()()
const daysToEvents = eventsRepository.getEventsForMonths()()
o(daysToEvents.size).equals(1)
o.check(daysToEvents.get(startOfDay)?.length).equals(1)
o.check(daysToEvents.get(startOfDay)?.[0].event).equals(event)
@ -168,13 +163,11 @@ o.spec("CalendarEventRepositoryTest", function () {
const eventStartDate = new Date(2025, 7, 26, 10, 0, 0)
const startOfDay = getStartOfDay(eventStartDate).getTime()
const daysToEventsMock: DaysToEvents = new Map([[startOfDay, []]])
when(
calendarFacade.updateEventMap(matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything()),
).thenResolve(
when(calendarFacadeMock.updateEventMap(matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything())).thenResolve(
daysToEventsMock, // Provide a initialized Map with an empty day
)
// Making sure EventRepository.daysToEvents and EventRepository.loadedMonths is initialized
await calendarEventsRepositoryMock.loadMonthsIfNeeded([eventStartDate], stream(false), null)
await eventsRepository.loadMonthsIfNeeded([eventStartDate], stream(false), null)
const newCalendarGroupId = "newCalendarGroupId"
const newCalendarInfo: CalendarInfo = object()
@ -208,7 +201,7 @@ o.spec("CalendarEventRepositoryTest", function () {
await entityEventsListener!([calendarEventUpdate], newCalendarGroupId)
// Assert
const daysToEvents = calendarEventsRepositoryMock.getEventsForMonths()()
const daysToEvents = eventsRepository.getEventsForMonths()()
o(daysToEvents.size).equals(1)
o.check(daysToEvents.get(startOfDay)?.length).equals(1)
o.check(daysToEvents.get(startOfDay)?.[0].event).equals(event)
@ -221,19 +214,20 @@ o.spec("CalendarEventRepositoryTest", function () {
const eventStartDate = new Date(2025, 7, 26, 10, 0, 0)
const pendingEvent = createTestEntity(CalendarEventTypeRef, {
_ownerGroup: initialCalendarGroupId,
_id: [pendingEventsListId, "event"],
_id: ["listId", "event"],
startTime: eventStartDate,
endTime: new Date(2025, 7, 26, 23, 0, 0),
pendingInvitation: true,
})
when(entityClientMock.load(CalendarEventTypeRef, matchers.anything())).thenResolve(pendingEvent)
const startOfDay = getStartOfDay(eventStartDate).getTime()
const daysToEventsMock: DaysToEvents = new Map([[startOfDay, []]])
when(
calendarFacade.updateEventMap(matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything()),
).thenResolve(daysToEventsMock)
when(calendarFacadeMock.updateEventMap(matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything())).thenResolve(
daysToEventsMock,
)
// Making sure EventRepository.daysToEvents and EventRepository.loadedMonths is initialized
await calendarEventsRepositoryMock.loadMonthsIfNeeded([eventStartDate], stream(false), null)
await eventsRepository.loadMonthsIfNeeded([eventStartDate], stream(false), null)
// Act
const calendarEventUpdate: EntityUpdateData = object()
@ -244,7 +238,7 @@ o.spec("CalendarEventRepositoryTest", function () {
await entityEventsListener!(updates, initialCalendarGroupId)
// Assert
const daysToEvents = calendarEventsRepositoryMock.getEventsForMonths()()
const daysToEvents = eventsRepository.getEventsForMonths()()
o(daysToEvents.size).equals(1)
let eventsOfToday = daysToEvents.get(startOfDay) ?? []
o.check(first(eventsOfToday)?.flags.isGhost).equals(true)
@ -259,7 +253,7 @@ o.spec("CalendarEventRepositoryTest", function () {
const eventStartDate = new Date(2020, 7, 26, 10, 0, 0)
const pendingEvent = createTestEntity(CalendarEventTypeRef, {
_ownerGroup: initialCalendarGroupId,
_id: [pendingEventsListId, "event"],
_id: ["listId", "event"],
startTime: eventStartDate,
endTime: new Date(2020, 7, 26, 23, 0, 0),
repeatRule: createTestEntity(CalendarRepeatRuleTypeRef, {
@ -268,17 +262,18 @@ o.spec("CalendarEventRepositoryTest", function () {
endValue: null,
interval: "1",
}),
pendingInvitation: true,
})
when(entityClientMock.load(CalendarEventTypeRef, matchers.anything())).thenResolve(pendingEvent)
const currentDay = new Date(2025, 1, 10)
const startOfDay = getStartOfDay(currentDay).getTime()
const daysToEventsMock: DaysToEvents = new Map([[startOfDay, []]])
when(
calendarFacade.updateEventMap(matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything()),
).thenResolve(daysToEventsMock)
when(calendarFacadeMock.updateEventMap(matchers.anything(), matchers.anything(), matchers.anything(), matchers.anything())).thenResolve(
daysToEventsMock,
)
// Making sure EventRepository.daysToEvents and EventRepository.loadedMonths is initialized
await calendarEventsRepositoryMock.loadMonthsIfNeeded([new Date(startOfDay)], stream(false), null)
await eventsRepository.loadMonthsIfNeeded([new Date(startOfDay)], stream(false), null)
// Act
const calendarEventUpdate: EntityUpdateData = object()
@ -289,7 +284,7 @@ o.spec("CalendarEventRepositoryTest", function () {
await entityEventsListener!(updates, initialCalendarGroupId)
// Assert
const daysToEvents = calendarEventsRepositoryMock.getEventsForMonths()()
const daysToEvents = eventsRepository.getEventsForMonths()()
const eventsOfToday = daysToEvents.get(startOfDay) ?? []
o(daysToEvents.size).equals(28)
o.check(first(eventsOfToday)?.flags.isGhost).equals(true)

View file

@ -554,6 +554,7 @@ o.spec("CalendarImporter", function () {
excludedDates: [],
endValue: null,
}),
pendingInvitation: null,
}),
alarms: [],
},
@ -632,6 +633,7 @@ o.spec("CalendarImporter", function () {
status: CalendarAttendeeStatus.NEEDS_ACTION,
}),
],
pendingInvitation: null,
}),
alarms: [],
},
@ -711,6 +713,7 @@ o.spec("CalendarImporter", function () {
status: CalendarAttendeeStatus.NEEDS_ACTION,
}),
],
pendingInvitation: null,
}),
alarms: [],
},
@ -792,6 +795,7 @@ o.spec("CalendarImporter", function () {
status: CalendarAttendeeStatus.NEEDS_ACTION,
}),
],
pendingInvitation: null,
}),
alarms: [],
},
@ -861,6 +865,7 @@ o.spec("CalendarImporter", function () {
hashedUid: null,
description: "Some description",
location: "Brazil",
pendingInvitation: null,
}),
alarms: [],
},
@ -884,6 +889,7 @@ o.spec("CalendarImporter", function () {
sequence: "1",
summary: "bkbkbkb",
recurrenceId: null,
pendingInvitation: null,
}),
alarms: [],
}
@ -973,6 +979,7 @@ o.spec("CalendarImporter", function () {
hashedUid: null,
description: "Some description",
location: "Brazil",
pendingInvitation: null,
}),
alarms: [],
},
@ -1040,6 +1047,7 @@ o.spec("CalendarImporter", function () {
uid: "test@tuta.com",
hashedUid: null,
repeatRule: null,
pendingInvitation: null,
}),
alarms: [
{
@ -1116,6 +1124,7 @@ o.spec("CalendarImporter", function () {
uid: "test@tuta.com",
hashedUid: null,
repeatRule: null,
pendingInvitation: null,
}),
alarms: [
{
@ -1190,6 +1199,7 @@ o.spec("CalendarImporter", function () {
uid: "test@tuta.com",
hashedUid: null,
repeatRule: null,
pendingInvitation: null,
}),
alarms: [],
},
@ -1278,6 +1288,7 @@ END:VCALENDAR`
uid: "test@tuta.com",
hashedUid: null,
sequence: "1",
pendingInvitation: null,
}),
alarms: [],
},
@ -1318,6 +1329,7 @@ END:VCALENDAR`
sequence: "2",
uid: "test@tuta.com",
hashedUid: null,
pendingInvitation: null,
}),
alarms: [alarmOne, alarmTwo],
},
@ -1375,6 +1387,7 @@ END:VCALENDAR`
excludedDates: [],
advancedRules: [],
}),
pendingInvitation: null,
}),
alarms: [],
},
@ -1429,6 +1442,7 @@ END:VCALENDAR`
timeZone: "",
excludedDates: [],
}),
pendingInvitation: null,
}),
alarms: [],
},

View file

@ -2,7 +2,6 @@ import o from "@tutao/otest"
import {
CalendarEvent,
CalendarEventAttendeeTypeRef,
CalendarEventsRefTypeRef,
CalendarEventTypeRef,
CalendarEventUpdateTypeRef,
CalendarGroupRoot,
@ -12,7 +11,7 @@ import {
} from "../../../src/common/api/entities/tutanota/TypeRefs.js"
import { downcast, hexToUint8Array, neverNull, stringToUtf8Uint8Array } from "@tutao/tutanota-utils"
import { CalendarModel } from "../../../src/calendar-app/calendar/model/CalendarModel.js"
import { CalendarAttendeeStatus, CalendarMethod, OperationType } from "../../../src/common/api/common/TutanotaConstants.js"
import { CalendarAttendeeStatus, CalendarMethod, GroupType, OperationType } from "../../../src/common/api/common/TutanotaConstants.js"
import { DateTime } from "luxon"
import { EntityEventsListener, EventController } from "../../../src/common/api/main/EventController.js"
import { Notifications } from "../../../src/common/gui/Notifications.js"
@ -35,7 +34,7 @@ import { verify } from "@tutao/tutanota-test-utils"
import type { WorkerClient } from "../../../src/common/api/main/WorkerClient.js"
import { FileController } from "../../../src/common/file/FileController.js"
import { func, instance, matchers, object, when } from "testdouble"
import { elementIdPart, getElementId, getListId, listIdPart } from "../../../src/common/api/common/utils/EntityUtils.js"
import { elementIdPart, getElementId, listIdPart } from "../../../src/common/api/common/utils/EntityUtils.js"
import { createDataFile } from "../../../src/common/api/common/DataFile.js"
import { SessionKeyNotFoundError } from "../../../src/common/api/common/error/SessionKeyNotFoundError.js"
import { createTestEntity } from "../TestUtils.js"
@ -58,16 +57,15 @@ o.spec("CalendarModel", function () {
o.spec("calendar event updates", function () {
let restClientMock: EntityRestClientMock
let groupRoot: CalendarGroupRoot
const loginController = makeLoginController()
const loginControllerMock = makeLoginController()
const alarmsListId = neverNull(loginController.getUserController().user.alarmInfoList).alarms
const alarmsListId = neverNull(loginControllerMock.getUserController().user.alarmInfoList).alarms
o.beforeEach(function () {
groupRoot = createTestEntity(CalendarGroupRootTypeRef, {
_id: "groupRootId",
longEvents: "longEvents",
shortEvents: "shortEvents",
pendingEvents: createTestEntity(CalendarEventsRefTypeRef, { list: "pendingEvents" }),
})
restClientMock = new EntityRestClientMock()
restClientMock.addElementInstances(groupRoot)
@ -213,25 +211,23 @@ o.spec("CalendarModel", function () {
o.spec("CalendarMethod.REQUEST", function () {
o.spec("Pending events", function () {
const groupInfoId: IdTuple = ["groupInfoList", "groupInfoId"]
const groupId = "groupId"
o("New invite", async function () {
// Arrange
const uid = "uid"
const sender = "sender@example.com"
const restClientMock = new EntityRestClientMock()
const workerClient = makeWorkerClient()
const calendarFacade = makeCalendarFacade(
const workerClientMock = makeWorkerClient()
const calendarFacadeMock = makeCalendarFacade(
{
getEventsByUid: (_loadUid) => Promise.resolve(null),
},
restClientMock,
)
restClientMock.addElementInstances(groupRoot)
const model = init({
workerClient,
workerClient: workerClientMock,
restClientMock,
calendarFacade,
})
// Act
@ -240,7 +236,7 @@ o.spec("CalendarModel", function () {
contents: [
{
event: createTestEntity(CalendarEventTypeRef, {
uid,
uid: "uid",
attendees: [
createTestEntity(CalendarEventAttendeeTypeRef, {
address: createTestEntity(EncryptedMailAddressTypeRef, {
@ -257,109 +253,23 @@ o.spec("CalendarModel", function () {
// ASSERT
// checks that update route was not taken
verify(calendarFacade.updateCalendarEvent(matchers.anything(), matchers.anything(), matchers.anything()), { times: 0 })
verify(calendarFacadeMock.updateCalendarEvent(matchers.anything(), matchers.anything(), matchers.anything()), { times: 0 })
// get pending list ID for calendar
const defaultCalGroup = await model.getDefaultCalendarGroupRoot()
const pendingListId = defaultCalGroup.pendingEvents?.list ?? ""
// capture list ID from created event
// capture created event
const eventCaptor = matchers.captor()
verify(calendarFacade.saveCalendarEvent(eventCaptor.capture(), matchers.anything(), matchers.anything()))
verify(calendarFacadeMock.saveCalendarEvent(eventCaptor.capture(), matchers.anything(), matchers.anything()))
const capturedEventInput: CalendarEvent = eventCaptor.value
o.check(getListId(capturedEventInput)).equals(pendingListId)
o.check(capturedEventInput.pendingInvitation).equals(true)
})
o("Update any field but startTime of an event that hasn't been interacted yet", async function () {
o("Update any field but startTime", async function () {
/*
Simple updates for pending events are treated the same way as a invitation that was already replied.
Simple updates for pending events are treated the same way as an invitation that was already replied.
The event doesn't have to be deleted and recreated
*/
const uid = "uid"
const sender = "sender@example.com"
const pendingListId = groupRoot.pendingEvents?.list || ""
const startTime = new Date()
const existingEvent = createTestEntity(CalendarEventTypeRef, {
_id: [pendingListId, "eventId"], // list ID is being set arbitrarily.
_ownerGroup: groupRoot._id,
summary: "v1",
sequence: "1",
uid,
organizer: createTestEntity(EncryptedMailAddressTypeRef, {
address: sender,
}),
startTime,
})
const workerClient = makeWorkerClient()
const calendarFacade = makeCalendarFacade(
{
getEventsByUid: (loadUid) =>
uid === loadUid
? Promise.resolve({
progenitor: existingEvent,
alteredInstances: [],
})
: Promise.resolve(null),
},
restClientMock,
)
const model = init({
workerClient,
restClientMock,
calendarFacade,
})
const sentEvent = createTestEntity(CalendarEventTypeRef, {
summary: "v2",
uid,
sequence: "2",
organizer: createTestEntity(EncryptedMailAddressTypeRef, {
address: sender,
}),
startTime,
})
await model.processCalendarData(sender, {
method: CalendarMethod.REQUEST,
contents: [
{
event: sentEvent as CalendarEventProgenitor,
alarms: [],
},
],
})
const eventCaptor = matchers.captor()
const alarmsCaptor = matchers.captor()
const oldEventCaptor = matchers.captor()
verify(calendarFacade.updateCalendarEvent(eventCaptor.capture(), alarmsCaptor.capture(), oldEventCaptor.capture()), { times: 1 })
const updatedEvent: CalendarEvent = eventCaptor.value
o(updatedEvent.summary).equals(sentEvent.summary)
o(updatedEvent.sequence).equals(sentEvent.sequence)
const oldEvent = oldEventCaptor.value
o(oldEvent).deepEquals(existingEvent)
o(getElementId(updatedEvent)[0]).deepEquals(getElementId(oldEvent)[0])
o(getElementId(updatedEvent)[1]).deepEquals(getElementId(oldEvent)[1])
o.check(getListId(updatedEvent)).equals(pendingListId)
})
o("Update to already replied event", async function () {
const uid = "uid"
const sender = "sender@example.com"
const alarm = createTestEntity(AlarmInfoTypeRef, {
_id: "alarm-id",
})
restClientMock.addListInstances(
createTestEntity(UserAlarmInfoTypeRef, {
_id: [alarmsListId, alarm._id],
alarmInfo: alarm,
}),
)
const startTime = new Date()
const existingEvent = createTestEntity(CalendarEventTypeRef, {
_id: ["listId", "eventId"],
@ -370,8 +280,8 @@ o.spec("CalendarModel", function () {
organizer: createTestEntity(EncryptedMailAddressTypeRef, {
address: sender,
}),
alarmInfos: [[alarmsListId, alarm._id]],
startTime,
pendingInvitation: true,
})
const workerClient = makeWorkerClient()
const calendarFacade = makeCalendarFacade(
@ -400,6 +310,7 @@ o.spec("CalendarModel", function () {
}),
startTime,
})
await model.processCalendarData(sender, {
method: CalendarMethod.REQUEST,
contents: [
@ -409,27 +320,28 @@ o.spec("CalendarModel", function () {
},
],
})
const eventCaptor = matchers.captor()
const alarmsCaptor = matchers.captor()
const oldEventCaptor = matchers.captor()
verify(calendarFacade.updateCalendarEvent(eventCaptor.capture(), alarmsCaptor.capture(), oldEventCaptor.capture()))
const updatedEvent = eventCaptor.value
const updatedAlarms = alarmsCaptor.value
const oldEvent = oldEventCaptor.value
o(updatedEvent.summary).equals(sentEvent.summary)
o(updatedEvent.sequence).equals(sentEvent.sequence)
o(updatedAlarms).deepEquals([alarm])
o(oldEvent).deepEquals(existingEvent)
verify(calendarFacade.updateCalendarEvent(eventCaptor.capture(), matchers.anything(), oldEventCaptor.capture()), { times: 1 })
const oldEvent: CalendarEvent = oldEventCaptor.value
o.check(oldEvent).deepEquals(existingEvent)
const updatedEvent: CalendarEvent = eventCaptor.value
o.check(updatedEvent._id).deepEquals(oldEvent._id)
o.check(updatedEvent.summary).equals(sentEvent.summary)
o.check(updatedEvent.sequence).equals(sentEvent.sequence)
o.check(updatedEvent.pendingInvitation).equals(true)
})
o("CalendarModel.createPendingEvent is called when unaccepted pending event start time is updated", async function () {
o("Change to event start time should create a new pending event and delete the old one", async function () {
const uid = "uid"
const sender = "sender@example.com"
const pendingListId = groupRoot.pendingEvents?.list || ""
const startTime = new Date()
const existingEvent = createTestEntity(CalendarEventTypeRef, {
_id: [pendingListId, "eventId"], // list ID is being set arbitrarily.
_id: ["listId", "eventId"],
_ownerGroup: groupRoot._id,
summary: "v1",
sequence: "1",
@ -438,6 +350,7 @@ o.spec("CalendarModel", function () {
address: sender,
}),
startTime,
pendingInvitation: true,
})
const workerClient = makeWorkerClient()
@ -461,7 +374,6 @@ o.spec("CalendarModel", function () {
const newStartTime = new Date(startTime)
newStartTime.setMinutes(newStartTime.getMinutes() + 42)
const sentEvent = createTestEntity(CalendarEventTypeRef, {
summary: "v2",
uid,
@ -483,17 +395,92 @@ o.spec("CalendarModel", function () {
const newEventCaptor = matchers.captor()
const oldEventCaptor = matchers.captor()
verify(calendarFacade.saveCalendarEvent(newEventCaptor.capture(), matchers.anything(), oldEventCaptor.capture()), { times: 1 })
const oldEvent: CalendarEvent = oldEventCaptor.value
const newEvent: CalendarEvent = newEventCaptor.value
o(getListId(newEvent)).equals(pendingListId)
o(oldEvent).deepEquals(existingEvent)
o.check(oldEvent).deepEquals(existingEvent)
o.check(oldEvent._id).notEquals(newEvent._id)
o.check(oldEvent.uid).equals(newEvent.uid)
o.check(newEvent.pendingInvitation).equals(true)
})
})
o("Update to already replied event", async function () {
const uid = "uid"
const sender = "sender@example.com"
const alarm = createTestEntity(AlarmInfoTypeRef, {
_id: "alarm-id",
})
restClientMock.addListInstances(
createTestEntity(UserAlarmInfoTypeRef, {
_id: [alarmsListId, alarm._id],
alarmInfo: alarm,
}),
)
const startTime = new Date()
const existingEvent = createTestEntity(CalendarEventTypeRef, {
_id: ["listId", "eventId"],
_ownerGroup: groupRoot._id,
summary: "v1",
sequence: "1",
uid,
organizer: createTestEntity(EncryptedMailAddressTypeRef, {
address: sender,
}),
alarmInfos: [[alarmsListId, alarm._id]],
startTime,
})
const workerClient = makeWorkerClient()
const calendarFacade = makeCalendarFacade(
{
getEventsByUid: (loadUid) =>
uid === loadUid
? Promise.resolve({
progenitor: existingEvent,
alteredInstances: [],
})
: Promise.resolve(null),
},
restClientMock,
)
const model = init({
workerClient,
restClientMock,
calendarFacade,
})
const sentEvent = createTestEntity(CalendarEventTypeRef, {
summary: "v2",
uid,
sequence: "2",
organizer: createTestEntity(EncryptedMailAddressTypeRef, {
address: sender,
}),
startTime,
})
await model.processCalendarData(sender, {
method: CalendarMethod.REQUEST,
contents: [
{
event: sentEvent as CalendarEventProgenitor,
alarms: [],
},
],
})
const eventCaptor = matchers.captor()
const alarmsCaptor = matchers.captor()
const oldEventCaptor = matchers.captor()
verify(calendarFacade.updateCalendarEvent(eventCaptor.capture(), alarmsCaptor.capture(), oldEventCaptor.capture()))
const updatedEvent = eventCaptor.value
const updatedAlarms = alarmsCaptor.value
const oldEvent = oldEventCaptor.value
o(updatedEvent.summary).equals(sentEvent.summary)
o(updatedEvent.sequence).equals(sentEvent.sequence)
o(updatedAlarms).deepEquals([alarm])
o(oldEvent).deepEquals(existingEvent)
})
o("event is re-created when the start time changes", async function () {
const uid = "uid"
const sender = "sender@example.com"
@ -799,7 +786,7 @@ function makeWorkerClient(): WorkerClient {
return downcast({})
}
function makeLoginController(): LoginController {
function makeLoginController(groupId: Id = "groupId", groupInfoId: IdTuple = ["list", "elementId"]): LoginController {
const alarmInfoList = createTestEntity(UserAlarmInfoListTypeTypeRef, {
alarms: "alarms",
})
@ -810,13 +797,8 @@ function makeLoginController(): LoginController {
alarmInfoList,
})
// As any because we can't modify userSettingsGroupRoot as it is read-ony
// and "when" is not working correctly
;(userController as any).userSettingsGroupRoot = object({
defaultCalendar: "groupRootId",
})
when(userController.getCalendarMemberships()).thenReturn([])
const groupMembership = createTestEntity(GroupMembershipTypeRef, { group: groupId, groupType: GroupType.Calendar, groupInfo: groupInfoId })
when(userController.getCalendarMemberships()).thenReturn([groupMembership])
const contactGroupMembership = createTestEntity(GroupMembershipTypeRef, { group: "contactGroup" })
when(userController.getContactGroupMemberships()).thenReturn([contactGroupMembership])