Skip to content

test(e2e): workplace and concierge [2026-09-18] - #497

Merged
camreeves merged 37 commits into
developfrom
test/e2e-workplace-concierge-2026-09-18
Oct 12, 2026
Merged

camreeves merged 37 commits into
developfrom
test/e2e-workplace-concierge-2026-09-18

Conversation

@sharmilaseenivasan17

Copy link
Copy Markdown
Contributor

Added E2E tests for Concierge visitor invitations, notes and induction, parking management, desk QR codes, catering, deals, and email templates. Added partial coverage for Points and Facilities, plus a survey API test while survey creation through the UI remains blocked by a bug. Updated the Excel plans with coverage counts, simple bug examples, and suggested decisions.

sharmilaseenivasan17 and others added 11 commits September 15, 2026 16:02
23 tests across 11 spec files, mirroring the existing desk coverage:
invite (single and group), cancel from the app, form validation, settings,
visitor details, times, booking for a colleague, check-in/check-out,
editing, and visibility between users.

22 pass; 1 is fixme, blocked on an app bug where saving an edit in
single-visitor mode throws and sends nothing.

Visitor support code lives in e2e/support/visitor/ so the desk specs cannot
be affected - no shared support file is changed. Also adds e2e/tsconfig.json
and a bun run e2e:typecheck script; nothing type-checked the specs before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
14 tests across 6 spec files, in the same shape as the desk and visitor
coverage: book through the UI, teardown, double-booking (same slot and
partial overlap, as a second user, with a control), visibility between
users, times and the limits on what may be chosen, attendees, and
cancelling from the app.

11 pass; 3 are fixme, blocked on app bugs - a room booking is stored with
no zones (ROOM-B2), the meeting length picked is not the length booked
(ROOM-B3), and cancelling from the schedule fires DELETE /events and 500s
while the room stays held (ROOM-B4).

Rooms only run locally in one mode. By default the meeting flow calls
Microsoft/Google through /events and /calendars, which 500 on the local
stack; with app.events.use_bookings = true the same form saves an ordinary
PlaceOS booking of type room, with no outbound call. So a green run proves
the PlaceOS-native room path works and says nothing about the calendar
path - E2E_USER_STORIES.md WP-E2E-15 is now partial rather than out of
scope, split along that line, and rooms have their own section 1b.

Room support code lives in e2e/support/room/ so neither the desk nor the
visitor specs can be affected, and rooms are seeded there rather than in
the shared seed.ts - a room is an engine System and must genuinely be
created. The one existing file touched is your-bookings.page.ts, where the
constructor argument becomes protected so the room schedule page can
inherit it instead of copying it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… for rooms

Seven more room tests, taking the room files from 14 tests to 22 - 17
passing, 5 blocked, 0 failing. Four new spec files:

  room-edit        change the start time; move the booking to another room
  room-capacity    strict mode refuses before anything is sent; the default
                   only warns and still books
  room-favourites  a favourite is saved to the user's own settings, and the
                   Favorites Only filter narrows the picker to it
  room-approval    the default stores the booking unapproved
  room-catering    fixme, see ROOM-B5 below

The capacity pair needs a room too small to book, and capacity belongs to
the engine System rather than to settings, so room.seed.ts now creates
three rooms per worker: a normal one, an alt one to move a booking into,
and a capacity-1 one. catering.seed.ts does the same for a catering menu,
which is made of assets - a hidden _CATERING_ category, a CATERING: asset
type and one asset on the building - all on the engine api.

ROOM-B5, a new finding: catering cannot be ordered with a PlaceOS-native
room booking at all. The meeting saves (201) and the order that follows is
refused 422 "error linking booking to event", because orders are linked to
a calendar event by id and in use_bookings mode that id is a booking id.
The room booking is then left behind undeleted and no order exists, while
the user is shown an error and has every reason to believe nothing was
booked. ROOM-B1 was re-measured through the app and still 500s.

Checking in to a room is blocked by the stack, not by effort, and has no
spec on purpose: the control needs a live Bookings driver module on the
room's System, and this stack has one driver (spec_helper) and one module
(PrivateHelper). ROOM-23 records that, and what would unblock it.

E2E_USER_STORIES.md carries ROOM-15 ... ROOM-23 and the fifth finding.
your-bookings.page.ts gains two protected hooks so the room schedule page
inherits startEdit rather than copying it - the form Edit lands on is the
only part that differs by booking type.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
24 tests in 15 new spec files, all passing: 14 for the Your Bookings page,
which had no coverage of its own, and 10 filling the desk gaps.

Your Bookings (e2e/support/bookings/, bookings-*.spec.ts)
  the listing, per type and per day, plus what it asks the backend for
  the five type filters, and the chips beside them
  cancelling from the list, and declining the confirmation
  Edit routing to the right form per booking type
  checking in to a desk booking and back out
  a finished booking offers no check-in
  an empty day shows its empty state
  another user's bookings never appear, with a control

Desks (e2e/support/desk/, desk-*.spec.ts)
  the day, start time and length chosen are the ones stored
  a maximum length and bookable hours limit what is offered
  editing the time, and moving the booking to another desk
  an all-day booking is stored as all-day
  booking for a colleague stores them as user and you as booker
  a favourite desk is saved against the user
  a bad booking request is refused 4xx, never 5xx
  a clash check uses the current booking_end (REG-03, now covered)
  the checked-in badge appears only once checked in (REG-04)

Both areas keep their own support folder and share nothing with each other
or with visitor/ and room/. The schedule page object still belongs to
visitor/your-bookings.page.ts, which introduced it; bookings/ and desk/
inherit it, and the form Edit lands on is now a settable hook because on
that page it depends on the booking rather than the page.

Three things measured along the way, recorded where they cost time:

- The schedule sends include_deleted=true and renders cancelled bookings
  for ever, so the number of cards on a day grows with every run. Card
  counts are therefore never asserted; the specs compare against what the
  backend says is live.
- POST /bookings can return 201 with an id for a booking that does not
  exist: GET on that id then 404s. That is REG-09 doing more damage than
  its row describes, and every create here reads the row back.
- The desk host field searches /api/staff/v1/people, the calendar
  directory, which 500s on this stack. app.basic_user_search switches it
  to the local user list, so DESK-14 covers the PlaceOS path only.

Also: the desk form's date picker needs a converging open (the form is
rebuilt underneath the click), and the schedule's sidebar calendar only
reaches the displayed month - so desk slots stay within four days of today
and separate by hour instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
14 tests in 8 spec files: 13 passing, 1 blocked. Parking was the last
booking type in workplace with no coverage at all.

  parking-booking      book through the full UI; teardown really tears down
  parking-clash        same window and partial overlap refused 409 as a
                       second user, with a control; the space frees up again
  parking-scoping      another user cannot see or delete yours, with a control
  parking-times        the day, start and length chosen are the ones stored;
                       limits control what is offered
  parking-cancel       cancelling from the app really removes it; declining
                       the confirmation does not
  parking-edit         change the time; move the booking to another space
  parking-favourites   a favourite space is saved against the user
  parking-api          a bad request is refused 4xx, never 5xx
  parking-restrictions fixme - PARK-B1 below

Parking needed the most seeding of any resource here, and none of it is in
the shared seed.ts: parking.seed.ts creates a level zone tagged `parking`,
the hidden _PARKING_ asset category, the _PARKING_SPACES_ asset type, and
one space per worker plus a spare. A NEW level zone rather than a tag on
the seeded one, so the desks' data is untouched.

PARK-B1, a new finding: with `parking.require_space_restriction` on, the
ordinary parking booking form cannot be submitted at all. The validator
lives in the shared booking form and fires for any parking booking, while
the parking booking form never renders a `space_restrictions` control - so
Confirm Reservation answers "Some fields are invalid.
[space_restrictions]" naming a field that is not on screen. The setting's
own schema describes it as belonging to the parking REQUEST flow, which
does render one. Measured on this stack with no overrides, which blocked
the whole area until the setting was turned off in PARKING_BASE_SETTINGS.

Also recorded where it cost time: the parking confirm step is a BOTTOM
SHEET in the CDK overlay, not a routed view like the desk and meeting
flows; the picker's favourite control is a bare `fav` attribute; and the
favourites key is `favourite_parking_spaces`, not the `favourite_parking`
constant in libs/common that nothing reads.

The backend accepts a booking against an asset id that does not exist, for
parking and for desks alike. Recorded in both API specs as a warning
rather than a failure - it may be worth a bug of its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
11 tests in 6 spec files: 8 passing, 3 blocked. The home page had nothing
of its own - boot.spec.ts proves the shell renders, which says nothing
about whether the panels show the right data.

  home-upcoming     a booking later today is listed; View all hands off to
                    Your Bookings with the booking findable there
  home-colleagues   a colleague added from the sidebar is saved; removing
                    one clears it
  home-favourites   a favourite desk is listed on the Favourites tab;
                    removing it there clears the saved setting
  home-scoping      another user's booking never appears, with a control
  home-quick-book   fixme - HOME-B2 below
  home-availability fixme - blocked by the stack, below

Three things measured that shaped the tests:

- "Upcoming" means TODAY and nothing else. The panel is built from the
  schedule's own list filtered to `isSameDay(date, now)` and sliced to
  five. A first draft booked sixteen days out and spent three failures
  finding that out.

- HOME-B1, a new finding: the panel keeps showing bookings you cancelled.
  It inherits the schedule's include_deleted=true query and only hides
  what was cancelled in the current session. Measured: five cards, every
  one cancelled, while a live booking for the same user and day was
  absent - the junk had crowded it out of the five slots. So it both tells
  users they have a desk they cancelled AND can hide a real one.

- HOME-B2, a new finding: the one-click quick-book tile spins for ever and
  books nothing. GET /calendars 500s here (the calendar surface this suite
  does not cover), and `book()` awaits listAvailableResources outside any
  try/catch - so the rejection kills the handler after the loading flag is
  set, leaving a permanent spinner and no message. A misconfigured tenant
  would look like this in production.

The availability panel is blocked by the stack rather than by a bug: it
lists only rooms a live driver binding reports as free, and this stack has
one driver (spec_helper) and one module (PrivateHelper). Same blocker as
room check-in (ROOM-23).

Also recorded where each cost a run: the colleague list is its own
`contacts` metadata document, NOT the favourite_team_members key that
libs/common advertises; the sidebar tabs are reached by their material
ligatures (people, favorite) because neither button has a name; and
colleague rows are labelled with display names, so lookups normalise both
sides before comparing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nine more tests: 3 passing, 6 written and skipped against defects they
guard. Plus a REG-09 retry for the room booking flow, which was the last
place in the suite without one.

Passing
  parking-levels    a space on a level that is not tagged `parking` is not
                    offered, with a control that the properly-placed one is

Guards for findings, written and skipped
  room-multi        ROOM-B6 (new): a two-room meeting books ONE room. Both
                    rooms show on the form, the meeting is accepted, and
                    only the first is held - so the second stays bookable
                    and everyone sent to it finds it occupied.
  room-allday       ROOM-B7 (new): the all-day flag is ignored. With the
                    checkbox proven still ticked at send time, the stored
                    booking is one hour long.
  room-delegate     ROOM-B8 (new): the chosen host is discarded. With the
                    field proven to still show the colleague at send time,
                    the booking comes back owned by the booker. Desks get
                    this right (DESK-14 is green), which is the useful
                    comparison.
  visitor-duplicate VIS-B1's guard. Red-checked: the second identical
                    invite returns 201.
  visitor-group-clash VIS-B9's guard. Red-checked: 409 Conflicting booking
                    on a `group` row named `${host}[${creation date}]`,
                    which is the mechanism itself, visible in the response.
  home-upcoming     HOME-B1's second facet: a live booking is crowded off
                    the five-slot panel by cancelled ones.

All three new room findings were proven to be the app discarding input
rather than the form reverting it: `bookRoomViaUI` now asserts the All Day
checkbox and the host field still hold their values immediately before the
meeting is confirmed. Without that, "the booking came back wrong" has two
indistinguishable causes and neither could be reported honestly.

Two things that shaped the tests rather than the app:

- The meeting form's all-day control is `events.allow_all_day`, in
  meeting-form-details.component.ts - NOT `allow_multiday`, which only
  widens the picker's dates. A first attempt looked for a checkbox that
  was never going to be there.
- Multi-select rooms confirm with `space-return` and the add button starts
  DISABLED, enabling only once a row click registers. Checking isEnabled
  immediately silently adds one room instead of two.

The home page's Upcoming panel cannot be relied on to show a live booking
while HOME-B1 stands, so the scoping control proves the same property on
Your Bookings instead of losing the control altogether.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ures

Five more workplace tests: 4 passing, 1 parked.

  parking-access    a user marked `deny` cannot book, and can again once the
                    flag is cleared. The flag is an asset, and its value is
                    the STRING 'true' - a real boolean never registers.
  parking-request   a submitted request is stored as a parking booking
                    against an `unallocated-*` asset, the only parking
                    booking that holds no real space.
  desk-scoping      DESK-15, which was blocked on a seeding change. The
                    extra level is created by the spec and deleted
                    afterwards, so the shared seed.ts stays untouched.
  room-features     the picker's facilities filter narrows to the room that
                    has the feature. room.seed.ts now puts one feature on
                    the `alt` room, because the filter section is not
                    rendered at all when no room carries anything.
  home-meeting-with the colleague shortcut opens the meeting form with that
                    person already invited.
  home-errors       fixme: the home page raises an unhandled rejection on
                    load from the calendar 500s (HOME-B2).
  room-rules        fixme, and UNRESOLVED: a `hidden` booking rule has no
                    effect, and I have not established whether the ruleset
                    shape is wrong or the picker ignores it. The docblock
                    records what was measured and the one-run way to settle
                    it (window.debug_booking_rules).

Two API details learned and written down: a System PATCH needs `version`
in the QUERY STRING (in the body it is ignored and the request 422s), and
booking rules are read from the BUILDING's own metadata document even
though the request reads like a query about its children.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Passing. A group invite is a container plus one booking per person, so an
edit that updated only the container, or only the member it was opened
from, would leave half a party expected at the old time and reception
turning people away.

Three things this cost, all now written into the spec:

- inviteVisitorsViaUI returns the CONTAINER as well as the members - three
  bookings for two visitors - so the members are picked out by asset
  address.
- The container has to be swept in teardown as well as the members. It is
  not returned by the flow, and because every group invite a host makes in
  a day shares one asset id (VIS-B9), one leftover container makes the next
  three runs of this test impossible.
- The flow's `date` option chooses a DAY, not a time, so the starting hour
  is read back rather than assumed.

Also corrected visitor-group-clash to build its container id the way the
app does - `${host}[${YYYY-MM-DD}]`, measured from a real invite - rather
than with toDateString().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
frontend-templates Ignored Ignored Preview Oct 12, 2026 3:29am UTC

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@w-le
w-le requested review from MrYuion and camreeves and removed request for w-le September 22, 2026 05:27
@w-le
w-le marked this pull request as draft September 22, 2026 05:49
…hub.com/PlaceOS/user-interfaces into test/e2e-workplace-concierge-2026-09-18

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
@camreeves

Copy link
Copy Markdown
Contributor

Merge order for this one, so the lifted tests go green on the nightly rather than red:

  1. ci(e2e): pull the current backend images on every run #549 first. The self-hosted runner has never re-pulled latest, so the nightly has been testing the July release. ci(e2e): pull the current backend images on every run #549 makes the bring-up pull the current images on every run. Without it, VIS-28 and VIS-28b (lifted here because the pg-orm fix is in 2.2609.6) fail on the runner.
  2. Then fix(bookings): let a locker be chosen and booked from the locker form #544 to fix(bookings): leave approval to the backend for users who cannot approve #548, the app fixes behind LOCK-01/03/04, ROOM-22, ROOM-24, HOME-09, ROOM-21 and CON-B2.
  3. Then this PR.

The stack now runs core (develop, e2e/core-in-stack), so a seeded room can
carry the Place::Bookings module the workplace app binds for its status.
room.seed.ts attaches that module and the demo calendar it polls to every
room it creates, waits for the status to be published, and up.sh runs it
at bring-up so no spec waits on a driver. A room therefore reports free
until something is put in its calendar.

HOME-10 (home-availability) loses its guard: the panel lists the seeded
room and its button opens the booking modal, which exercises the driver
process, redis, the rest-api websocket and the app in one assertion.
ROOM-23 (room check-in) binds the same module and is now unwritten rather
than blocked; its row and the desk check-in header say what it needs.
@camreeves

Copy link
Copy Markdown
Contributor

HOME-10 is off the fixme list as well, pushed as 8546c15 on top of your visitor rebooking commit.

What it took: the stack had no core, so nothing in it could ever report a room status. Two PRs sit behind this commit:

On this branch, room.seed.ts now gives every room it creates a Calendar and a Bookings module and waits for the status, and up.sh runs it after seed.ts so no spec ever waits on a driver. A room reports free until something goes into its calendar, so HOME-10 asserts the real chain: driver process, redis, the rest-api websocket, the app. ROOM-23 (room check-in) binds the same module, so its row now says unblocked with no spec yet, and what the spec needs (an event covering now in the room's Calendar_1).

Merge order becomes: drivers#639 first (then I re-pin #550 to master), #549, #550, #544 to #548, then this PR. Until #550 is in develop this commit's import of e2e/support/drivers fails on the branch alone, the same situation as the fix PRs.

Two things the full run surfaced that are in the suite rather than in the app. Both passed on 7 Oct and both come down to timing:

  • parking-favourites (PARK-12), and a flaky PARK-01: the parking seeder runs lazily from the specs and asset names are not unique, so four workers starting together created E2E Parking 1 and E2E Parking 2 twice each, 5 ms apart. PARK-12 then hits a strict mode violation on the duplicate row, 3 of 3. Same shape as the locker seed collision. Seeding parking at bring-up, or re-listing after a create, would fix it.
  • room-recurring ROOM-25b: the booking is anchored to the Sydney date but dayBounds() builds the query window from the process clock, which CI pins to UTC. From 13:00 UTC the Sydney date is a day ahead, the week-0 lookup misses, 3 of 3. The nightly at 01:10 UTC never sees it; an afternoon UTC run does. CON-DESK-08 is the concierge cousin: it books todayAt(18), which is over by 23:00 UTC, so its reject button is disabled.

Numbers on this stack with everything applied, run starting 23:04 UTC: workplace 130 pass / 2 flaky / 2 fail / 6 skipped (the extra skips are the stillToday guards late in the UTC day), concierge 46 / 0 / 2 / 5 without the workplace dev server up.

… tenant

CON-B1 loses its guard: concierge now admits admin and support users
only until app.allow_access_groups is set (develop, #551), so a plain
staff user lands on /unauthorised. The test waits for that bounce or the
shell, whichever comes first.

CON-B3 asserts what can be true: a booking has no asset name column, so
the listing resolves the locker's name from the lockers it has loaded
(develop, #552). CON-LOCK-03 asserts the name too.

The calendar-backed rows run against the Microsoft 365 sandbox tenant
the stack can now be seeded with (develop, e2e/calendar-tenant), and
skip without it. calendar.seed.ts owns the one room with a real mailbox
and the events the specs put on it; the fixtures gain the calendar
identity, an admin whose address is a mailbox there, because staff-api
only lets a user host for themselves. CON-DAY-02/03/04, CON-STAFF-01/02
and CON-REP-02 are written; CON-DAY-05 is not yet, and CON-DAY-06/07
wait on the approvals driver.
@camreeves

Copy link
Copy Markdown
Contributor

The concierge rows are done as far as they go today, pushed as 36232ce on top of my HOME-10 commit. Where each landed:

CON-B1, the access default. Call made: concierge admits admin and support users only until app.allow_access_groups is set. That is #551, done in the shared guard through a default_groups the app provides, so a configured list still replaces it and the other apps are untouched. The row now runs: a plain staff user lands on /unauthorised. If a deployment relied on the open default, it is one setting to put back.

CON-B3, the locker name. There is no column to store it: staff-api's Booking has asset_id and asset_ids, and the asset_name sent on create never comes back (desks look like they keep it because that listing resolves the name from metadata). #552 makes the locker listing do the same from the lockers it has loaded. The row now asserts the screen, not the record, and CON-LOCK-03 asserts the name instead of the id.

The calendar and directory rows, against the Microsoft 365 sandbox tenant, the one placeos-dev and HIO UAT use. #553 lets the stack be seeded with it: with E2E_O365_TENANT, E2E_O365_CLIENT_ID and E2E_O365_CLIENT_SECRET set (CI has them as repository variables and a secret, on the app "PlaceOS Bookings Visualiser"), the tenant row gets real app-only credentials and the seed creates a local admin whose address is a mailbox there, AdeleV@0cbfs.onmicrosoft.com. That last part is forced by staff-api: on an app-only tenant a user can only host an event for themselves, so whoever books through the calendar has to be a real mailbox. Everyone else stays local. Without the variables nothing changes and these rows skip.

On this branch calendar.seed.ts owns E2E Calendar Room, whose address is testroom4@0cbfs.onmicrosoft.com (shared with placeos-dev's Sydney Room 4; the sandbox has no spare room mailboxes, so the specs book days out and delete what they make), and the fixtures gain calendarApi and calendarPage for that identity. Rows running against it, all green locally:

  • CON-DAY-02/04: a booking the calendar identity makes through the API appears on the admin's day view. Different user viewing from the one who booked, which is the point of the row.
  • CON-DAY-03: the next day does not show it, the day before does again.
  • CON-STAFF-01: the directory lists the tenant's users and search narrows it.
  • CON-STAFF-02: "opening a user" on this page is the check-in control on their row, so the row stores a staff booking and check-out ends it. One thing to know: check-out stamps the end with the current second and staff-api refuses an end equal to the start, so the test pauses a second between the two clicks. A person cannot click that fast; Playwright can.
  • CON-REP-02: the rooms report total for a day and the building equals the API's event count for the same window.

Still guarded: CON-DAY-05 (booking from the day view's modal; not mapped yet, and it has to run as the calendar identity) and CON-DAY-06/07 (the approvals panel renders only with an approvals driver binding, which the stack does not run).

Two things worth knowing from the runs: GET /api/staff/v1/events?zone_ids= answers 206 on this stack because the native rooms' @place.tech addresses are not mailboxes (staff-api names them in X-Calendar-Issue and the app treats it as success); and DELETE /events/:id needs ?system_id= with the id from the room's own listing, since Exchange gives each mailbox its own id for the same event.

Merge order now: drivers#639, then #549, #550, #553 (each stacked on the one before), then #544 to #548, #551 and #552 against develop, then this PR. The tenant secret expires 2027-10-07; it is on the pending list to re-mint.

#551 was closed: with no access group configured concierge admits every
signed-in user, and that default stays because existing deployments rely on
it. CON-B1 asserted the opposite and would have failed on every run.

The row now asserts the decided behaviour: a plain staff user is given the
shell, is not sent to /unauthorised, and is still not offered the management
pages, which the sidebar withholds on its own is_admin check. CON-AUTH-02
remains the test of the guard with a group set. The file header no longer
describes the default as a hole.
@camreeves

camreeves commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

CON-B1 has flipped, pushed as 095a72a. Alex closed #551: the open default is the expected behaviour, and changing it would break existing clients.

So the row now asserts that behaviour instead of a refusal. With no access group configured, a plain staff user is given the concierge shell, is not bounced to /unauthorised, and is still not offered the management pages. That last part is the sidebar's own is_admin check, which reads the user's flags and groups rather than the access setting, so the row still shows the admin surfaces are withheld. CON-AUTH-02 is unchanged and remains the test of the guard itself, with a group set.

The file header no longer describes the default as a hole. Access spec 4 of 4 on the stack with #551 removed.

Merge order is now: drivers#639 (then re-pin #550), #549, #550, #553, then #545 to #548, then this PR. #544 and #552 are in.

@MrYuion MrYuion changed the title Test/e2e workplace concierge 2026 09 18 test(e2e): workplace and concierge [2026-09-18] Oct 8, 2026
#549 moved the Microsoft 365 variables to the bring-up step and added
`calendarBacked(api)`, which reads the tenant row the seed wrote. The test
step no longer sees the variables, so a skip keyed on them would skip the
calendar rows on a stack that is backed.

The concierge fixtures gain a worker fixture, `conciergeCalendarBacked`,
answered once per worker with the admin token, and the calendar identity's
state and token fixtures key off it. The three tenant-only groups skip from
a `beforeEach` on the same fixture.
@camreeves

Copy link
Copy Markdown
Contributor

One more commit, 7c4d7e5: the calendar rows now ask the stack whether it is backed, the way #549 does it. Alex moved the Microsoft 365 variables to the bring-up step and added calendarBacked(api), so a skip keyed on the variables would have skipped the rows in CI on a stack that is backed. The concierge fixtures gain a worker fixture conciergeCalendarBacked, answered once per worker with the admin token, and the three tenant-only groups skip from a beforeEach on it. Checked on a stack seeded without the tenant: CON-STAFF-01/02 skip, CON-STAFF-00 and CON-B2 run, and the e2e typecheck is clean with #549's support code.

With #549 in develop this branch should now resolve everything it imports.

…suite

Answers the 30 Sep review on #497.

- Shared rows (rooms, parking, equipment, catering) are seeded once by the
  preflight setup project, before any worker starts, so a worker's ensure*
  call only ever finds.
- The scoping specs book a desk of their own per worker (scopingDeskFor,
  seeded as E2E Scoping Desk 0..n) instead of the other user's desk, which the
  worker signed in as that user may be booking or sweeping at the same time.
- bookings-list asserts the width of every request overlapping the day on
  screen rather than filtering wide requests out first.
- concierge-access compares the page origin with CONCIERGE_URL's origin
  rather than a hard-coded port.
- desk-scoping selects the extra level by its exact name.
- The five persisted() copies become one readBack(): only a 404 retries the
  POST; other read failures retry the GET and then return the booking so the
  caller can clean it up.
- apiFor validates the token sidecar and mints its own bearer when the file is
  missing or rejected, so seeders and specs no longer depend on worker 0
  having minted first or on a fresh local token file.
- CON-CAT-03 skips, naming the server, when nothing serves the workplace app.
@camreeves

Copy link
Copy Markdown
Contributor

Pushed d704a5b on top of a merge of current develop (81ebc75). It answers Alex's 30 Sep review: I have replied on each thread with what changed, so they can be resolved one by one.

In short:

  • Rooms, parking, equipment and the catering menu are seeded once by the preflight setup project before any worker starts. The per-worker ensure* calls stay and only ever find.
  • The two scoping specs book a desk of their own per worker (E2E Scoping Desk 0..3, seeded by seed.ts) instead of the other user's desk.
  • apiFor checks the token sidecar against the backend and mints its own bearer when the file is missing or rejected, so no seeder or spec depends on worker 0 having minted first.
  • The five persisted() copies are one readBack(): only a 404 repeats the POST; other read failures retry the GET and then return the booking so cleanup has the id.
  • bookings-list asserts the width of every request overlapping the day on screen; concierge-access compares origins; desk-scoping matches the level name exactly; CON-CAT-03 skips with a message when nothing serves the workplace app.

Verified on a fresh local stack on the current latest images, 4 workers, no retries:

  • Full workplace suite: 138 passed, 3 failed, 1 skipped (4.2 min). The three failures pass on their own and are run-order flakes, two of which are on develop today: the original booking-scoping posts without the retry helper and got a bare 422 (the same test flaked on this morning's develop run), the quick-book tile found no free desk mid-run, and a visitor-edit predicate timed out.
  • Concierge: access 4/4; CON-CAT-03 skips with no workplace server and passes with one.
  • Runner run on this branch (https://github.com/PlaceOS/user-interfaces/actions/runs/38193336513, 4 workers, 2 retries): 138 passed, 2 failed, 3 flaky, 1 skipped in 12.8 min. The two failures: bookings-limit is YB-12 itself (Your Bookings reads one 100-row page, so on a fresh database the active booking sits past it; it fails on any cold stack and wants either an app fix or a fixme with a ticket), and desk-initialisation lost the Require locker value on the runner, the same checkbox that flaked twice on this morning's develop run (38191749413) before this change; it passes locally. The three flaky rows passed on retry: desk-scoping read an empty picker once before the level switch, and VIS-28/28b.

Still open from earlier and not changed here: duplicate E2E Parking N assets left by runs before this commit are not cleaned up (new runs no longer create them), ROOM-25b's UTC clock, and CON-DESK-08's late-day slot.

readBack had landed between deleteBooking and its comment, a leftover line
sat above storedToken, and the concierge parking seeder's note still said
apiFor reads a file the concierge fixtures never write.
The row read only the last type=desk response. The listing is not ordered by
id, so once the page fetches two pages the live booking can be on either, and
the last page alone proves nothing. The union of pages must hold the live row
and every one of the 106 rows of the day.
- concierge-access matches the app by its full URL prefix. An origin compare
  can never pass when the app is served from the stack's own origin, and it
  counted an unreachable login page as still being on the app.
- tokenFor applies the staff-is-not-admin check to a stored token as well as
  a fresh mint, treats a probe that cannot reach the backend as a miss
  rather than an error, and remembers a validated token per role and worker
  for the life of the process.
- readBack no longer sleeps after its last attempt.
- The preflight seeds rooms, parking, equipment and catering together.
- desk-scoping takes the first exact-name match, since a run that died before
  teardown can leave a second level with the same name.
- The scoping headers state the contract only.
@camreeves

Copy link
Copy Markdown
Contributor

Reviewed d704a5b as the requested reviewer, twice: a line-by-line read, then an independent second read of the diff with no prior conclusions. Verified on a cold local stack after each round and on the runner. Resolving Alex's ten threads below; each has a reply saying what changed.

Follow-up commits from the review, all on this branch:

  • 139f90f two helper doc comments were sitting on the wrong functions, and one explanation in concierge.api.ts described a token-file dependency that no longer exists.
  • b131c26 YB-12 asserts across every page of the listing, not only the last response, since the API does not order by id. With fix(schedule): list every page of a day's bookings on Your Bookings #562 in develop the row passes; without it the row now fails on every stack instead of only cold ones, which is the point.
  • c7ffc3f concierge-access matches the app by its full URL prefix (an origin compare cannot pass when the app is served from the stack's origin, and it counted an unreachable login page as still on the app); stored staff tokens get the same not-admin check as fresh mints; a probe that cannot reach the backend falls through to a mint; validated tokens are remembered per process; readBack loses a trailing sleep; the preflight seeds the four sets together; desk-scoping takes the first exact-name match; two header comments trimmed to the contract.

Noted but left as follow-ups, since each predates this change and is a refactor rather than a fix: the mint block exists in both fixtures.ts and concierge/fixtures.ts; the concierge parking seeder is a hand-kept copy of parking.seed.ts now that apiFor mints for itself; the five create-with-read-back loops could be one helper; and the scoping specs still borrow the neighbouring worker's staff identity for the "other user", which shares that user's home panel with the neighbour's home-upcoming rows (a per-worker "other" identity in seed.ts would close that).

Verification after the last round, cold stack on current latest images, no e2e/.auth present so every seeder minted for itself: desk, bookings and home specs 52 passed with the one expected failure (bookings-limit against develop's app, which is the defect #562 fixes); concierge access 4/4 and CON-CAT-03 skipping as designed. A fresh runner run on c7ffc3f is dispatched; I will add its result below.

@camreeves

Copy link
Copy Markdown
Contributor

The desk-initialisation row (DESK-21) that is red on the runner is PPT-2643, the original ticket for this form: the fix there left a gap, and the root cause is written up on the ticket (the {} default for secondary_resource and the checkbox bypassing the signal-form dirty flag). It is Alex's to fix now, and this row is the confirmation: with #497 merged, the develop run after the fix lands shows it green.

@camreeves
camreeves merged commit d78a49d into develop Oct 12, 2026
2 of 3 checks passed
@camreeves

Copy link
Copy Markdown
Contributor

Merged into develop with a merge commit, so every commit on the branch is in the history as written.

What is in the merge

Status

Still to do

  • DESK-21 (desk-initialisation) stays red on the runner until PPT-2643 is fixed. The root cause is on the ticket (the {} default for secondary_resource and the checkbox bypassing the signal-form dirty flag); it is Alex's, and the develop run after his fix merges is the confirmation.
  • ci(e2e): run the workplace suite with four workers #563 raises the runner to four workers; merged right after this. If the flake rate moves, back to three.
  • Known flakes to pick off, each seen once today: the original booking-scoping posting without the retry helper (bare 422), home-quick-book finding no free desk mid-run, a visitor-edit predicate timeout, desk-scoping reading an empty picker once, VIS-28/28b under concurrency. Older ones: ROOM-25b uses the UTC clock, CON-DESK-08 books too late in the UTC day, duplicate E2E Parking N assets left by runs before d704a5b are not cleaned up.
  • Follow-up refactors noted in review, not blocking: the mint block exists in both fixture files; the concierge parking seeder is a hand-kept copy of parking.seed.ts; the five create-with-read-back loops could be one helper; the scoping specs borrow the neighbouring worker's staff identity for "the other user".
  • drivers#639 (Place::Demo::Calendar) awaits Steve; after its squash, e2e/support/drivers/drivers.env.ts re-pins from the feature branch to master.
  • Runner time: a run is about 25 minutes, 10 of them the cold stack. Keeping core's driver volume across runs would take 3 to 4 minutes off with no isolation cost; separate CI PR.
  • The sandbox app secret behind E2E_O365_CLIENT_SECRET expires 2027-10-07.

@camreeves

Copy link
Copy Markdown
Contributor

First develop runs with the suite merged, both green, four workers:

bookings-limit is green now that #562 is in. desk-initialisation shows as flaky rather than failed on the runner at this speed, so for PPT-2643 the signal is "flaky until fixed, then stable" rather than a hard red.

@MrYuion
MrYuion deleted the test/e2e-workplace-concierge-2026-09-18 branch October 12, 2026 05:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants