Fold the pre-deploy review findings into the calendar cutover
A fresh-context review before deploying found two things that would have broken production, both in the runbook rather than the code. The deploy order named only migration 003. Production has none of the three - 001 and 002 were only ever applied to the dev database - and the new API reads the columns they add on every request, so following it literally would have 500'd every calendar call including the anonymous feed the public website uses. Step 4 now carries a numbered checklist with a verification query. APP_ORIGINS replaces the code's default list rather than adding to it, so naming calendar.nachklang.art in DEFAULT_APP_ORIGINS is not enough if that variable is set on the vhost - and its failure mode is the quiet one the config already warns about, where everything works except sign-out. Added to the same checklist. Also from the review: The two operands of the read guard on /json/next and /ical were swapped so the password check short-circuits first. They are side-effect free, so the order was free - but the old one put an admin-database query in front of the public feed for any caller holding a .nachklang.art cookie, which is a dependency that feed has never had. Two tests now assert the admin database is not consulted at all. /:calendar/json/next had no route-level test, despite being the endpoint the public website actually calls and the property named as load-bearing. Covered now, along with the rest of its credential matrix. Migrations 001 and 002 gained IF NOT EXISTS. They are applied by hand with no tracking table, so a partial re-run should be a no-op rather than an error that aborts the rest of the paste. Verified by applying all three twice to a throwaway container and diffing against the dev schema. Swagger: two descriptions still claimed authentication was required where the public calendar needs none, the calendar enum omitted `birthdays`, and a `createdBy` request-body field was documented and read but never persisted - misleading in a way that suggests a client can set authorship. Removed. The CORS comment describing the calendar's query-parameter sessions is no longer true and was rewritten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,7 @@ beforeEach(() => {
|
||||
process.env.MEMBER_CREDENTIAL = 'member-secret';
|
||||
(EventService.getAllEvents as any).mockResolvedValue([]);
|
||||
(EventService.getAllEventsAdmin as any).mockResolvedValue([]);
|
||||
(EventService.getNextUpcomingEvent as any).mockResolvedValue({eventId: 1, name: 'Konzert'});
|
||||
(EventService.createEvent as any).mockResolvedValue(1);
|
||||
(EventService.updateEvent as any).mockResolvedValue(1);
|
||||
(EventService.moveEvent as any).mockResolvedValue(true);
|
||||
@@ -127,6 +128,42 @@ describe('reading', () => {
|
||||
await request(app).get('/calendar/events/public/json').expect(200);
|
||||
});
|
||||
|
||||
// The endpoint www.nachklang.art actually calls for its next-event teaser.
|
||||
// Tested separately from /json because it takes a different code path - it
|
||||
// has no admin view and no editor branch - so covering /json proves nothing
|
||||
// about it, and its failure is invisible until someone notices the website
|
||||
// has gone quiet.
|
||||
it('serves the next upcoming event anonymously on the public calendar', async () => {
|
||||
await request(app).get('/calendar/events/public/json/next').expect(200);
|
||||
|
||||
// And without asking the admin database who the caller is: the public
|
||||
// feed must not acquire a dependency it has never had.
|
||||
expect(auth.api.getSession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('refuses the next upcoming event on a restricted calendar without a credential', async () => {
|
||||
await request(app).get('/calendar/events/members/json/next').expect(403);
|
||||
});
|
||||
|
||||
it('serves the next upcoming event to a shared password', async () => {
|
||||
await request(app)
|
||||
.get('/calendar/events/members/json/next')
|
||||
.query({password: 'member-secret'})
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('serves the next upcoming event to a signed-in editor', async () => {
|
||||
signedInAs(['calendar']);
|
||||
|
||||
await request(app).get('/calendar/events/members/json/next').expect(200);
|
||||
});
|
||||
|
||||
it('does not consult the admin database for the anonymous public iCal export', async () => {
|
||||
await request(app).get('/calendar/events/public/ical').expect(200);
|
||||
|
||||
expect(auth.api.getSession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps the shared password working on the iCal export', async () => {
|
||||
(EventService.getAllEvents as any).mockResolvedValue([]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user