CU-04: Microsoft / Outlook calendar integration
Priority: P2 Accounts/sessions: Outlook host ravikant0909@outlook.com (logged into Calendo via "Sign in with Microsoft", Outlook calendar connected); access to outlook.com calendar + outlook.com mail in the same browser profile. Invitee identity uses P1's Gmail plus-alias ravikantguptaofficial+inv-<RUNID>@gmail.com so the confirmation email lands in the single searchable P1 Gmail inbox (mail.google.com). Parallel-safe: Yes — does not rewrite global host availability; only creates/removes throwaway calendar events and one throwaway event type + one booking, all scoped by RUNID. Exclusive (rewrites global host availability?): No. Estimated time: 35 minutes. L3 reality checks: Outlook calendar event create (booking) + delete (cancellation), and an Outlook BUSY event suppressing a booking-page slot. Email confirmation L3 in P1 Gmail.
Goal
This suite proves the Outlook/Microsoft mirror of the Google Calendar integration: a host who signed into Calendo with Microsoft and connected their Outlook calendar gets true two-way calendar sync. Specifically, (1) a BUSY event sitting in the host's real Outlook calendar during their available hours removes that slot from the public booking page (conflict detection via Microsoft Graph calendarView, filtering busy/tentative/oof), and (2) when an invitee books a free slot, Calendo writes the meeting into the real Outlook calendar, and (3) cancelling the Calendo booking removes the event from the real Outlook calendar. This matters because Outlook/Microsoft 365 users are a large share of professional schedulers; if conflict detection or event write-back silently fails, the host gets double-booked or their Outlook calendar drifts from Calendo. It also documents the Microsoft consent screen (including the unverified-publisher warning) and the offline_access refresh-token behavior so the human reviewer knows the OAuth posture.
Preconditions
- The browser profile MUST have an active Calendo session for the Outlook host
ravikant0909@outlook.com. Openhttps://calendo.dev/dashboard/— if it redirects to/auth/login.html, this is a precondition failure: STOP, do not perform a cold Microsoft password login, and FLAG it per00-setup-preconditions.md. The whole suite then becomes manual residue. - The browser profile MUST be able to open
https://outlook.com/calendarandhttps://outlook.com/mailalready signed in asravikant0909@outlook.com. If Outlook prompts for a password, STOP and FLAG as a precondition failure → mark this suite manual residue (it cannot be verified in-browser without a live Outlook session). Do NOT type the password. - The Outlook calendar SHOULD already be connected to Calendo (see Step 1 — the suite confirms this and only triggers the OAuth flow if it is missing). If the OAuth consent screen demands a password, treat as a precondition failure and FLAG; do not log in cold.
- P1 Gmail (
mail.google.comasravikantguptaofficial@gmail.com) MUST be open/logged in for the email L3 check. If not available, the email L3 check becomes manual residue but the calendar L3 checks can still run. - The Outlook host MUST have at least a default weekly availability schedule with some daytime hours, and the ability to create event types. This suite creates its own throwaway event type; it does NOT modify global availability.
- If ANY precondition is missing, FLAG it explicitly in the results report and do not improvise alternative logins.
Test data
- RUNID: pick a fresh UTC token at execution time, e.g.
20260601-1530. Use the SAME RUNID for every artifact below. - Event type name:
CU04 Outlook <RUNID>→ Calendo will auto-slug tocu04-outlook-<RUNID>(verify the actual slug from the event type's booking link; slugify lowercases, replaces spaces with-). - Event duration: 30 minutes.
- Outlook BUSY blocker event (created in outlook.com): subject
CU04 BUSY <RUNID>, status/Show-as = Busy, placed on the booking target day during host available hours (see Step 5 for exact time selection). - Invitee for the real booking: name
CU04 Invitee <RUNID>, emailravikantguptaofficial+inv-<RUNID>@gmail.com. - Host slug: read from the dashboard booking link (
#bookingLinkValue, the part afteruser=). Record it as<HOST_SLUG>. - Public booking URL:
https://calendo.dev/booking/?user=<HOST_SLUG>&event=cu04-outlook-<RUNID>.
Steps
Order matters: connect/confirm Outlook FIRST (Step 1), then create the event type (Step 2), then create the BUSY blocker in Outlook BEFORE checking the booking page (Steps 5–6) so the conflict test is valid. Only AFTER confirming the busy slot is suppressed do you remove the blocker and book a free slot (Steps 7–10), then cancel (Step 11). Doing the BUSY test before the booking test avoids the two interfering.
- Action Go to
https://calendo.dev/dashboard/and open the Availability tab (sidebar link.sidebar-nav a[data-tab="availability"]), then open the calendar-settings sub-panel (#avail-panel-calendar-settings, heading "Calendars to check for conflicts"). Read the connections list (#calSettingsConnections). → Expect A connection card (.cal-connection-card) labeled "Outlook Calendar" (.cal-connection-name) with a disconnect "×" button (.avail-btn-remove,onclick="disconnectCalendar(<id>)"). If present, Outlook is already connected — skip to Step 2. Capture screenshot:cu04-01-outlook-connected.png. → [L2]
- Action (only if Step 1 shows NO Outlook card) Click "+ Connect calendar account" (
#calSettingsConnectBtn) which points to/api/calendars/connect/outlook(or use the onboarding/welcome dialog "Connect Outlook Calendar" link,<a href="/api/calendars/connect/outlook">). Observe the Microsoft consent screen atlogin.microsoftonline.com/common/oauth2/v2.0/authorize. → Expect Microsoft sign-in/consent forravikant0909@outlook.com. The consent requests scopes openid, email, profile, offline_access, Calendars.ReadWrite, OnlineMeetings.ReadWrite. If a blue "unverified app / publisher not verified" warning appears, RECORD it verbatim in the report (this is expected for the indie Calendo app and is a documentation item, not a failure). If Microsoft demands a password, STOP and FLAG precondition failure. After accepting, the browser returns to Calendo. → Expect Redirect lands onhttps://calendo.dev/dashboard/?calendar=connected(success). On the Availability tab the Outlook card now appears in#calSettingsConnections. Capture screenshot:cu04-02-outlook-consent.png(consent screen) andcu04-02b-connected-redirect.png. → [L1]
- Action Reload
https://calendo.dev/dashboard/, go to Availability → calendar settings, and confirm the Outlook connection persists; also check the primary-calendar dropdown (#calSettingsPrimarySelect) lists an "Outlook" option. → Expect Outlook still shown after reload; dropdown contains an Outlook entry. (This proves the connection is stored, not just a transient redirect.) → [L2]
- Action Go to the Overview tab (
.sidebar-nav a[data-tab="overview"]). Click "+ New" to create an event type (#overviewCreateEtBtn); in the modal/panel (#editEtModal/#editEtPanel) set Name =CU04 Outlook <RUNID>(#editEtName), Duration = 30 (#editEtDuration), then Save/Create (#editEtSaveBtn). → Expect Event type appears in the overview list (#overviewEventTypescontainsCU04 Outlook <RUNID>). Open its booking link and record the exact slug; build<BOOKING_URL> = https://calendo.dev/booking/?user=<HOST_SLUG>&event=cu04-outlook-<RUNID>. Capture screenshot:cu04-04-eventtype-created.png. → [L1]/[L2]
- Action (BUSY conflict setup — do BEFORE checking slots) Open
https://outlook.com/calendarasravikant0909@outlook.com. Pick the first weekday at least 2 days out that has open daytime slots on the Calendo booking page (you will cross-check in Step 6); call it<TARGET_DAY>. Create a new event: titleCU04 BUSY <RUNID>, on<TARGET_DAY>from 10:00–11:00 in the host's Outlook timezone, and set Show as = Busy (the default; do NOT set Free). Save. → Expect The eventCU04 BUSY <RUNID>is visible on<TARGET_DAY>10:00–11:00 in outlook.com, marked Busy. Capture screenshot:cu04-05-outlook-busy-created.png. → [L3] (real Outlook event created)
- Action In a fresh tab open
<BOOKING_URL>. Navigate the booking calendar (.calendar-nav-btn) to<TARGET_DAY>and click that day cell (.calendar-day.available). Inspect the time slots panel (#time-slots-panel/#time-slots-list, slot buttons.time-slot). → Expect There is no bookable 10:00 / 10:30 slot for<TARGET_DAY>(the 30-min slots overlapping 10:00–11:00 are absent). Slots outside 10:00–11:00 (e.g. 09:30 or 11:00) should still be present if within host hours. If the day shows "No available times" (.time-slots-empty) entirely, pick a different<TARGET_DAY>with more hours and repeat Steps 5–6 so the test isolates the 10:00 block. Capture screenshot:cu04-06-slot-suppressed.png. → [L3→L1] (real Outlook busy suppresses a real booking-page slot)
- Action Return to
https://outlook.com/calendar, open theCU04 BUSY <RUNID>event, and delete it. → Expect The blocker event no longer appears on<TARGET_DAY>in outlook.com. Capture screenshot:cu04-07-outlook-busy-deleted.png. → [L3]
- Action Reload
<BOOKING_URL>, navigate again to<TARGET_DAY>, click the day, and confirm the previously-suppressed slot has returned. → Expect A bookable slot at/around 10:00 (e.g. the 10:00.time-slot) is now present again — proving conflict detection is live, not cached. (If Outlook busy results are briefly cached, allow up to ~1 minute and reload once.) Capture screenshot:cu04-08-slot-restored.png. → [L3→L1]
- Action (book a free slot) Still on
<BOOKING_URL>at<TARGET_DAY>, click an available slot (.time-slot, choose the restored 10:00 slot or any free slot). On the confirm form fill Name =CU04 Invitee <RUNID>(#input-name), Email =ravikantguptaofficial+inv-<RUNID>@gmail.com(#input-email), then click Confirm (#btn-confirm). → Expect Confirmation screen shows title "Booking Confirmed" (.confirmation-title) and the manage links (#confirmation-managewith Cancel / Reschedule anchors). Record the booking time<BOOK_START>(the exact start datetime shown) and the cancel link href. Capture screenshot:cu04-09-booking-confirmed.png. → [L1]
- Action Go to the dashboard Bookings tab (
.sidebar-nav a[data-tab="bookings"]) and confirm the new booking is listed forCU04 Invitee <RUNID>on<TARGET_DAY>at<BOOK_START>. → Expect Booking row present with the invitee name and eventCU04 Outlook <RUNID>. → [L2]
- Action (cancel) Open the cancel link recorded in Step 9 (or
https://calendo.dev/booking/cancel.htmlreached via the confirmation page / confirmation email), and complete the cancellation. → Expect The page confirms the booking is cancelled; in the dashboard Bookings tab the row shows cancelled/removed. Capture screenshot:cu04-11-cancel-confirmed.png. → [L1]/[L2]
- Action (document token-refresh behavior — read-only note) No in-browser action forces a token refresh, but record the expected posture for the reviewer: Calendo requested
offline_access, so it stores a refresh token;getValidOutlookTokenrefreshes when the access token is within 5 minutes of expiry OR when no expiry is recorded; if Microsoft returns AADSTS700082 / AADSTS50173 (refresh token expired after ~90 days inactivity), the host must re-connect Outlook. If during ANY step above a calendar action silently no-ops (slot not suppressed, or event not written), check the Availability calendar-settings panel for a disconnected/error state and a "Calendar disconnected" indicator — that points to an expired refresh token and is a real failure to report. → Expect Documented note + no observed disconnect/error state during the run. → [L2]
L3 reality checks
A. Outlook busy suppresses a slot (Steps 5–8).
- Open
https://outlook.com/calendarasravikant0909@outlook.com. CONFIRM the eventCU04 BUSY <RUNID>exists on<TARGET_DAY>10:00–11:00, Show-as Busy (Step 5), then CONFIRM it is gone after deletion (Step 7). - On
<BOOKING_URL>CONFIRM the 10:00/10:30 slots are ABSENT while the busy event exists (Step 6) and PRESENT after deletion (Step 8).
B. Booking writes a real Outlook event (Step 9).
- Open
https://outlook.com/calendarasravikant0909@outlook.comand navigate to<TARGET_DAY>at<BOOK_START>. CONFIRM an event exists titledCU04 Outlook <RUNID> with CU04 Invitee <RUNID>(subject format is<EventType> with <Invitee>), duration 30 min, with attendeeravikantguptaofficial+inv-<RUNID>@gmail.com. Capture screenshot:cu04-L3-outlook-event-created.png.
C. Cancellation removes the Outlook event (Step 11).
- Reload
https://outlook.com/calendarat<TARGET_DAY>/<BOOK_START>. CONFIRM the eventCU04 Outlook <RUNID> with CU04 Invitee <RUNID>is GONE (deleted, not merely greyed). Capture screenshot:cu04-L3-outlook-event-deleted.png.
D. Email confirmation in P1 Gmail (Step 9).
- Open
https://mail.google.comasravikantguptaofficial@gmail.com. Search exactly:inv-<RUNID>. CONFIRM a booking-confirmation email addressed toravikantguptaofficial+inv-<RUNID>@gmail.comwhose subject/body referencesCU04 Outlook <RUNID>and<BOOK_START>, and contains working Cancel/Reschedule links. Capture screenshot:cu04-L3-email-confirmation.png. (If P1 Gmail is unavailable, mark this single check as manual residue; the calendar L3 checks A–C still gate Pass/Fail.)
Cleanup
Leave both host accounts clean. Perform regardless of pass/fail:
- Ensure the booking from Step 9 is cancelled (Step 11). If you skipped it, cancel it now via
https://calendo.dev/booking/cancel.htmlor the dashboard Bookings tab. - In
https://outlook.com/calendar, delete any remainingCU04 BUSY <RUNID>event AND anyCU04 Outlook <RUNID> with CU04 Invitee <RUNID>event (the cancellation should have removed the latter; if it lingers, delete manually and FLAG it as a write-back/cancel-sync bug). - In the Calendo dashboard, delete the throwaway event type
CU04 Outlook <RUNID>(Overview/Event Types → its delete/×). - Do NOT disconnect the Outlook calendar connection — it is a shared precondition for reruns. Only disconnect if you connected it fresh in Step 2 AND the reviewer wants the account left exactly as found; otherwise leave it connected and note it.
- In P1 Gmail, the confirmation/cancellation emails can be left (they are RUNID-scoped and searchable); optionally archive/trash anything matching
inv-<RUNID>to keep the inbox tidy.
Pass/Fail criteria
PASSES only if ALL are met:
- The Outlook calendar connection is present and persists across reload (Steps 1/3) — or was successfully (re)connected via Microsoft OAuth landing on
?calendar=connected(Step 2). [L1/L2] - A real Outlook BUSY event during host hours REMOVES the overlapping slot(s) from the public booking page (Step 6). [L3→L1]
- Deleting that Outlook event RESTORES the slot on the booking page (Step 8). [L3→L1]
- Booking a free slot produces a "Booking Confirmed" screen (Step 9) AND a real event titled
CU04 Outlook <RUNID> with CU04 Invitee <RUNID>appears in the Outlook calendar at<BOOK_START>with the invitee as attendee (L3 check B). - Cancelling the booking removes that event from the Outlook calendar (L3 check C).
- No "Calendar disconnected"/token-error state appears during the run (Step 12).
- (If P1 Gmail available) the confirmation email arrives and matches
inv-<RUNID>(L3 check D).
FAILS if: any slot is NOT suppressed by the busy event; the event is NOT written to Outlook; the cancellation leaves a stale Outlook event; the connection does not persist; or a token/disconnect error surfaces.
Evidence to capture
cu04-01-outlook-connected.png(connection card in Availability settings)cu04-02-outlook-consent.png+cu04-02b-connected-redirect.png(only if a fresh connect was needed; include the unverified-publisher warning if shown)cu04-04-eventtype-created.pngcu04-05-outlook-busy-created.png/cu04-07-outlook-busy-deleted.pngcu04-06-slot-suppressed.png/cu04-08-slot-restored.pngcu04-09-booking-confirmed.pngcu04-11-cancel-confirmed.pngcu04-L3-outlook-event-created.png/cu04-L3-outlook-event-deleted.pngcu04-L3-email-confirmation.png- Text notes:
<RUNID>,<HOST_SLUG>, resolved event slug,<TARGET_DAY>,<BOOK_START>, exact wording of any unverified-publisher warning, and any observed token/disconnect error.
Manual residue / cannot-verify
- If the Outlook Calendo session OR the outlook.com session requires a password mid-run, the entire suite is manual residue — a human must run it with a live Microsoft session.
- The unverified-publisher / app-verification status of the Calendo Microsoft app is documented, not fixed, here — Microsoft publisher verification is a human/admin task.
- True token-refresh /
offline_accessbehavior (refresh after expiry, AADSTS700082/50173 re-auth prompts, ~90-day inactivity expiry) cannot be force-triggered in a single browser session; only the no-error steady state is observed. Long-term refresh correctness is manual/observational residue. OnlineMeetings.ReadWrite(Teams auto-link generation) is in scope of the OAuth grant but creating/verifying a real Teams join link is out of scope for this suite (the event type here is not set to Teams location) — TBD/handed to a video-conferencing suite.- Whether the written Outlook event's reminders/alerts/organizer-vs-attendee semantics match the host's expectations is not asserted here.