Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 0 additions & 79 deletions packages/backend/src/event/services/event.service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import type { GaxiosError } from "gaxios";
import { ClientSession, Document, Filter, ObjectId, WithId } from "mongodb";
import {
Origin,
Priorities,
RRULE,
RRULE_COUNT_MONTHS,
RRULE_COUNT_WEEKS,
SOMEDAY_MONTHLY_LIMIT,
SOMEDAY_WEEKLY_LIMIT,
} from "@core/constants/core.constants";
Expand All @@ -14,22 +9,18 @@ import { Status } from "@core/errors/status.codes";
import { MapEvent } from "@core/mappers/map.event";
import {
CalendarProvider,
CompassEventStatus,
CompassThisEvent,
EventUpdatePayload,
EventUpdateSchema,
Params_DeleteMany,
Payload_Order,
Query_Event,
RecurringEventUpdateScope,
Result_DeleteMany,
Schema_Event,
Schema_Event_Core,
WithCompassId,
} from "@core/types/event.types";
import { gSchema$Event } from "@core/types/gcal";
import { IDSchema } from "@core/types/type.utils";
import { getCurrentRangeDates } from "@core/util/date/date.util";
import { CompassEventRRule } from "@core/util/event/compass.event.rrule";
import { isInstance, parseCompassEventDate } from "@core/util/event/event.util";
import { getGcalClient } from "@backend/auth/services/google.auth.service";
Expand All @@ -41,78 +32,8 @@ import gcalService from "@backend/common/services/gcal/gcal.service";
import mongoService from "@backend/common/services/mongo.service";
import { reorderEvents } from "@backend/event/queries/event.queries";
import { getReadAllFilter } from "@backend/event/services/event.service.util";
import { CompassSyncProcessor } from "@backend/sync/services/sync/compass.sync.processor";

class EventService {
createDefaultSomedays = async (userId: string, session?: ClientSession) => {
const { week, month } = getCurrentRangeDates();

const defaultWeekly: Schema_Event_Core = {
title: "⭐ That one thing...",
isAllDay: false,
isSomeday: true,
startDate: week.startDate,
endDate: week.endDate,
priority: Priorities.UNASSIGNED,
origin: Origin.COMPASS,
description: `... that you wanna do this week, but aren't sure when.\
\nKeep it here for safekeeping, then drag it over to the calendar once you're ready to commit times.\
\n\nThese sidebar events are:\
\n-filtered by the calendar week you're on\
\n-limited to ${SOMEDAY_WEEKLY_LIMIT} per week`,
user: userId,
};

const weeklyRepeat: Schema_Event_Core = {
title: "🪴 Water plants",
isAllDay: false,
isSomeday: true,
startDate: week.startDate,
endDate: week.endDate,
origin: Origin.COMPASS,
priority: Priorities.SELF,
description: `This event happens every week.\
\n\nRather than repeating forever, however, it'll stop after ${
RRULE_COUNT_WEEKS / RRULE_COUNT_MONTHS
} months.\
\n\nThis encourages frequent re-prioritizing, rather than running on autopilot indefinitely.`,
recurrence: {
rule: [RRULE.WEEK],
},
user: userId,
};

const monthlyRepeat: Schema_Event_Core = {
isAllDay: false,
isSomeday: true,
origin: Origin.COMPASS,
priority: Priorities.RELATIONS,
startDate: month.startDate,
endDate: month.endDate,
title: "🎲 Schedule game night",
recurrence: {
rule: [RRULE.MONTH],
},
description: `This one repeats once a month for ${RRULE_COUNT_MONTHS} months`,
user: userId,
};

return CompassSyncProcessor.processEvents(
[weeklyRepeat, monthlyRepeat, defaultWeekly].map((e) => {
const eventId = new ObjectId().toString();

return {
eventId,
userId,
payload: { ...e, _id: eventId } as CompassThisEvent["payload"],
status: CompassEventStatus.CONFIRMED,
applyTo: RecurringEventUpdateScope.THIS_EVENT,
};
}),
session,
);
};

/*
Deletes all of a user's events
REMINDER: this should only delete a user's *Compass* events --
Expand Down
9 changes: 1 addition & 8 deletions packages/backend/src/user/services/user.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import calendarService from "@backend/calendar/services/calendar.service";
import { UserError } from "@backend/common/errors/user/user.errors";
import { initSupertokens } from "@backend/common/middleware/supertokens.middleware";
import mongoService from "@backend/common/services/mongo.service";
import eventService from "@backend/event/services/event.service";
import priorityService from "@backend/priority/services/priority.service";
import userMetadataService from "@backend/user/services/user-metadata.service";
import userService from "@backend/user/services/user.service";
Expand Down Expand Up @@ -85,7 +84,6 @@ describe("UserService", () => {
expect(storedUser).not.toBeNull();

await priorityService.createDefaultPriorities(userId);
await eventService.createDefaultSomedays(userId);
await SyncDriver.createSync(storedUser!, true);
await userService.startGoogleCalendarSync(userId);

Expand Down Expand Up @@ -121,7 +119,7 @@ describe("UserService", () => {
});

describe("initUserData", () => {
it("creates the compass user with default resources", async () => {
it("creates the compass user with default priorities", async () => {
const gUser = UserDriver.generateGoogleUser();

EmailDriver.mockEmailServiceResponse();
Expand All @@ -141,11 +139,6 @@ describe("UserService", () => {
.find({ user: userId })
.toArray();
expect(priorities.length).toBeGreaterThan(0);

const somedayEvents = await mongoService.event
.find({ user: userId, isSomeday: true })
.toArray();
expect(somedayEvents.length).toBeGreaterThan(0);
});
});

Expand Down
2 changes: 0 additions & 2 deletions packages/backend/src/user/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,6 @@ class UserService {

await priorityService.createDefaultPriorities(cUser.userId, session);

await eventService.createDefaultSomedays(cUser.userId, session);

return cUser;
};

Expand Down
12 changes: 12 additions & 0 deletions packages/web/src/__tests__/utils/storage/indexeddb.test.util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const COMPASS_LOCAL_DB_NAME = "compass-local";
const DEMO_DATA_SEED_MIGRATION_FLAG = "compass.migration.demo-data-seed-v1";

export async function clearCompassLocalDb(): Promise<void> {
await new Promise<void>((resolve) => {
Expand All @@ -8,3 +9,14 @@ export async function clearCompassLocalDb(): Promise<void> {
request.onblocked = () => resolve();
});
}

/**
* Prepares empty storage for tests that expect no data.
* Clears localStorage and IndexedDB, then sets the demo-data-seed migration
* flag so it won't seed when storage initializes.
*/
export async function prepareEmptyStorageForTests(): Promise<void> {
localStorage.clear();
await clearCompassLocalDb();
localStorage.setItem(DEMO_DATA_SEED_MIGRATION_FLAG, "completed");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/**
* Tests for the demo data seed migration.
*/
import { Priorities } from "@core/constants/core.constants";
import { Event_Core } from "@core/types/event.types";
import dayjs from "@core/util/date/dayjs";
import { createMockTask } from "@web/__tests__/utils/factories/task.factory";
import {
StorageAdapter,
StoredTask,
} from "@web/common/storage/adapter/storage.adapter";
import { Task } from "@web/common/types/task.types";
import { demoDataSeedMigration } from "./demo-data-seed";

function createMockAdapter(): jest.Mocked<StorageAdapter> {
const tasksByDate = new Map<string, Task[]>();
const events: Event_Core[] = [];

return {
initialize: jest.fn().mockResolvedValue(undefined),
isReady: jest.fn().mockReturnValue(true),
getTasks: jest.fn().mockImplementation(async (dateKey: string) => {
return tasksByDate.get(dateKey) ?? [];
}),
getAllTasks: jest.fn().mockImplementation(async () => {
const allTasks: StoredTask[] = [];
for (const [dateKey, tasks] of tasksByDate.entries()) {
for (const task of tasks) {
allTasks.push({ ...task, dateKey });
}
}
return allTasks;
}),
putTasks: jest.fn().mockImplementation(async (dateKey: string, tasks) => {
tasksByDate.set(dateKey, tasks);
}),
putTask: jest.fn().mockResolvedValue(undefined),
deleteTask: jest.fn().mockResolvedValue(undefined),
moveTask: jest.fn().mockResolvedValue(undefined),
clearAllTasks: jest.fn().mockResolvedValue(undefined),
getEvents: jest.fn().mockResolvedValue(events),
getAllEvents: jest.fn().mockImplementation(async () => events),
putEvent: jest.fn().mockImplementation(async (event: Event_Core) => {
events.push(event);
}),
putEvents: jest.fn().mockImplementation(async (newEvents: Event_Core[]) => {
events.push(...newEvents);
}),
deleteEvent: jest.fn().mockResolvedValue(undefined),
clearAllEvents: jest.fn().mockResolvedValue(undefined),
getMigrationRecords: jest.fn().mockResolvedValue([]),
setMigrationRecord: jest.fn().mockResolvedValue(undefined),
};
}

describe("demoDataSeedMigration", () => {
beforeEach(() => {
jest.spyOn(console, "log").mockImplementation(() => {});
});

afterEach(() => {
jest.restoreAllMocks();
});

it("seeds demo data when storage is empty", async () => {
const adapter = createMockAdapter();

await demoDataSeedMigration.migrate(adapter);

expect(adapter.putEvents).toHaveBeenCalled();
expect(adapter.putTasks).toHaveBeenCalled();

// Verify events were created (6 total: 4 today + 2 someday)
const eventsCall = adapter.putEvents.mock.calls[0][0] as Event_Core[];
expect(eventsCall).toHaveLength(6);

// Verify tasks were created for 3 days
expect(adapter.putTasks).toHaveBeenCalledTimes(3);
});

it("skips seeding when events already exist", async () => {
const adapter = createMockAdapter();
adapter.getAllEvents.mockResolvedValue([
{
_id: "existing-event",
startDate: "2025-01-15T09:00:00Z",
endDate: "2025-01-15T10:00:00Z",
origin: "compass",
priority: "unassigned",
user: "user-1",
} as Event_Core,
]);

await demoDataSeedMigration.migrate(adapter);

expect(adapter.putEvents).not.toHaveBeenCalled();
expect(adapter.putTasks).not.toHaveBeenCalled();
});

it("skips seeding when tasks already exist", async () => {
const adapter = createMockAdapter();
adapter.getAllTasks.mockResolvedValue([
{ ...createMockTask(), dateKey: "2025-01-15" },
]);

await demoDataSeedMigration.migrate(adapter);

expect(adapter.putEvents).not.toHaveBeenCalled();
expect(adapter.putTasks).not.toHaveBeenCalled();
});

it("creates events with relative dates (not hardcoded)", async () => {
const adapter = createMockAdapter();

await demoDataSeedMigration.migrate(adapter);

const eventsCall = adapter.putEvents.mock.calls[0][0] as Event_Core[];
const today = dayjs().toYearMonthDayString();

// Check that at least one timed event starts today
const todayEvents = eventsCall.filter(
(e) => !e.isSomeday && e.startDate.startsWith(today),
);
expect(todayEvents.length).toBeGreaterThan(0);
});

it("creates tasks for today, yesterday, and tomorrow", async () => {
const adapter = createMockAdapter();

await demoDataSeedMigration.migrate(adapter);

const today = dayjs().toYearMonthDayString();
const yesterday = dayjs().subtract(1, "day").toYearMonthDayString();
const tomorrow = dayjs().add(1, "day").toYearMonthDayString();

const putTasksCalls = adapter.putTasks.mock.calls;
const dateKeys = putTasksCalls.map((call) => call[0]);

expect(dateKeys).toContain(today);
expect(dateKeys).toContain(yesterday);
expect(dateKeys).toContain(tomorrow);
});

it("creates yesterday tasks as completed", async () => {
const adapter = createMockAdapter();

await demoDataSeedMigration.migrate(adapter);

const yesterday = dayjs().subtract(1, "day").toYearMonthDayString();
const putTasksCalls = adapter.putTasks.mock.calls;
const yesterdayCall = putTasksCalls.find((call) => call[0] === yesterday);

expect(yesterdayCall).toBeDefined();
const yesterdayTasks = yesterdayCall![1] as Task[];
expect(yesterdayTasks.every((t) => t.status === "completed")).toBe(true);
});

it("creates events with all four priorities", async () => {
const adapter = createMockAdapter();

await demoDataSeedMigration.migrate(adapter);

const eventsCall = adapter.putEvents.mock.calls[0][0] as Event_Core[];
const priorities = new Set(eventsCall.map((e) => e.priority));

expect(priorities.has(Priorities.WORK)).toBe(true);
expect(priorities.has(Priorities.SELF)).toBe(true);
expect(priorities.has(Priorities.RELATIONS)).toBe(true);
expect(priorities.has(Priorities.UNASSIGNED)).toBe(true);
});

it("creates someday events for week and month", async () => {
const adapter = createMockAdapter();

await demoDataSeedMigration.migrate(adapter);

const eventsCall = adapter.putEvents.mock.calls[0][0] as Event_Core[];
const somedayEvents = eventsCall.filter((e) => e.isSomeday);

expect(somedayEvents).toHaveLength(2);
});
});
Loading