test(v1/events): update mock chains to cover new app_settings probes

The #592 fix added a devtools-detection probe, and the #592 follow-up
added a require_password probe + a branding-defaults whereIn().select().
Both shift the db() call indices the existing #550 test relied on, and
the branding probe needed `.select()` to resolve to an array (the mock
chain wasn't thenable, so `for..of` on the result threw → 500 on every
test that hit BASE_BODY).

Add `whereIn` + `selectResult` to buildChain so the branding probe
yields an iterable. Factor the three pre-slug app_settings chains into
a baseSettingsChains() helper and update each test's queued sequence
and toHaveBeenNthCalledWith / toHaveBeenCalledTimes expectations to
match the new shape. No behaviour change in v1/events.js — only the
test scaffolding moves.
This commit is contained in:
Paul Nothaft
2026-05-31 23:02:51 +02:00
parent 791e9974eb
commit 8c6525af01
@@ -18,12 +18,17 @@
const request = require('supertest'); const request = require('supertest');
const express = require('express'); const express = require('express');
const buildChain = ({ firstResult, insertResult, returningResult } = {}) => { const buildChain = ({ firstResult, insertResult, returningResult, selectResult } = {}) => {
const chain = { const chain = {
where: jest.fn().mockReturnThis(), where: jest.fn().mockReturnThis(),
whereIn: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(), andWhere: jest.fn().mockReturnThis(),
orWhere: jest.fn().mockReturnThis(), orWhere: jest.fn().mockReturnThis(),
select: jest.fn().mockReturnThis(), // `select` resolves to an array so `await db(...).whereIn(...).select(...)`
// gives an iterable result (used by the branding-defaults probe added in
// #592 follow-up). Tests that don't need it leave selectResult undefined
// and get `[]`, which is a safe no-op for any caller that iterates.
select: jest.fn().mockResolvedValue(selectResult ?? []),
first: jest.fn().mockResolvedValue(firstResult), first: jest.fn().mockResolvedValue(firstResult),
insert: jest.fn().mockReturnThis(), insert: jest.fn().mockReturnThis(),
returning: jest.fn().mockResolvedValue(returningResult ?? insertResult ?? [{ id: 1 }]), returning: jest.fn().mockResolvedValue(returningResult ?? insertResult ?? [{ id: 1 }]),
@@ -92,23 +97,28 @@ const BASE_BODY = {
require_password: false, require_password: false,
}; };
// db() call sequence for BASE_BODY (no feedback / devtools provided,
// require_password supplied so its probe is skipped, no customer_phone,
// no slug collision):
// 1. app_settings.where('event_default_feedback_enabled').first() (#550)
// 2. app_settings.where('enable_devtools_protection').first() (#592)
// 3. app_settings.whereIn([branding_logo_display_hero,...]).select(...) (#592 follow-up)
// Then slug probe, events insert, optional feedback insert.
const baseSettingsChains = () => [
buildChain({ firstResult: null }), // feedback default
buildChain({ firstResult: null }), // devtools default
buildChain({ selectResult: [] }), // branding whereIn → empty rows
];
describe('v1 POST /events — issue #550 (color_theme + feedback row)', () => { describe('v1 POST /events — issue #550 (color_theme + feedback row)', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
it('persists color_theme to the events row when provided', async () => { it('persists color_theme to the events row when provided', async () => {
// db() call sequence for this body (feedback_enabled omitted, no
// customer_phone, no slug collision):
// 1. app_settings.where('event_default_feedback_enabled').first()
// 2. events.where({ slug }).first() ← uniqueness probe
// 3. events.insert(...).returning('id')
// No event_feedback_settings insert because the global setting
// returns nothing (feedback stays off) — covered separately below.
const settingChain = buildChain({ firstResult: null });
const slugChain = buildChain({ firstResult: null }); const slugChain = buildChain({ firstResult: null });
const insertChain = buildChain({ returningResult: [{ id: 42 }] }); const insertChain = buildChain({ returningResult: [{ id: 42 }] });
db.__setImplementations(settingChain, slugChain, insertChain); db.__setImplementations(...baseSettingsChains(), slugChain, insertChain);
await request(buildApp()) await request(buildApp())
.post('/events') .post('/events')
@@ -123,11 +133,9 @@ describe('v1 POST /events — issue #550 (color_theme + feedback row)', () => {
}); });
it('accepts a JSON-encoded theme string and persists it verbatim', async () => { it('accepts a JSON-encoded theme string and persists it verbatim', async () => {
db.__setImplementations( const slugChain = buildChain({ firstResult: null });
buildChain({ firstResult: null }), const insertChain = buildChain({ returningResult: [{ id: 43 }] });
buildChain({ firstResult: null }), db.__setImplementations(...baseSettingsChains(), slugChain, insertChain);
buildChain({ returningResult: [{ id: 43 }] }),
);
const customTheme = JSON.stringify({ primaryColor: '#ff0066' }); const customTheme = JSON.stringify({ primaryColor: '#ff0066' });
await request(buildApp()) await request(buildApp())
@@ -135,26 +143,30 @@ describe('v1 POST /events — issue #550 (color_theme + feedback row)', () => {
.send({ ...BASE_BODY, color_theme: customTheme }) .send({ ...BASE_BODY, color_theme: customTheme })
.expect(201); .expect(201);
const insertedRow = db.mock.results[2].value.insert.mock.calls[0][0]; const insertedRow = insertChain.insert.mock.calls[0][0];
expect(insertedRow.color_theme).toBe(customTheme); expect(insertedRow.color_theme).toBe(customTheme);
}); });
it('creates event_feedback_settings row when feedback_enabled=true is sent', async () => { it('creates event_feedback_settings row when feedback_enabled=true is sent', async () => {
// 3 db() calls when feedback_enabled is sent explicitly (the // feedback_enabled provided → feedback probe SKIPPED. Sequence:
// settings probe is skipped because feedbackEnabledInput !== undefined): // 1. devtools probe
// 1. slug probe, 2. events insert, 3. feedback insert // 2. branding probe (whereIn → select)
// 3. slug probe
// 4. events insert
// 5. event_feedback_settings insert
const devtoolsChain = buildChain({ firstResult: null });
const brandingChain = buildChain({ selectResult: [] });
const slugChain = buildChain({ firstResult: null }); const slugChain = buildChain({ firstResult: null });
const insertChain = buildChain({ returningResult: [{ id: 50 }] }); const insertChain = buildChain({ returningResult: [{ id: 50 }] });
const feedbackInsertChain = buildChain(); const feedbackInsertChain = buildChain();
db.__setImplementations(slugChain, insertChain, feedbackInsertChain); db.__setImplementations(devtoolsChain, brandingChain, slugChain, insertChain, feedbackInsertChain);
await request(buildApp()) await request(buildApp())
.post('/events') .post('/events')
.send({ ...BASE_BODY, feedback_enabled: true }) .send({ ...BASE_BODY, feedback_enabled: true })
.expect(201); .expect(201);
// db('event_feedback_settings') is the 3rd invocation. expect(db).toHaveBeenNthCalledWith(5, 'event_feedback_settings');
expect(db).toHaveBeenNthCalledWith(3, 'event_feedback_settings');
const feedbackRow = feedbackInsertChain.insert.mock.calls[0][0]; const feedbackRow = feedbackInsertChain.insert.mock.calls[0][0];
expect(feedbackRow).toMatchObject({ event_id: 50 }); expect(feedbackRow).toMatchObject({ event_id: 50 });
@@ -172,39 +184,43 @@ describe('v1 POST /events — issue #550 (color_theme + feedback row)', () => {
}); });
it('honours the event_default_feedback_enabled global when body omits feedback_enabled', async () => { it('honours the event_default_feedback_enabled global when body omits feedback_enabled', async () => {
// settings probe returns a serialized "true" fallback should kick // Feedback probe returns serialized "true" fallback kicks in and
// in and the feedback row should still be written. // the feedback insert runs. Sequence: feedback probe, devtools probe,
const settingChain = buildChain({ // branding probe, slug, insert, feedback insert (6 calls total).
const feedbackProbe = buildChain({
firstResult: { setting_key: 'event_default_feedback_enabled', setting_value: 'true' }, firstResult: { setting_key: 'event_default_feedback_enabled', setting_value: 'true' },
}); });
const devtoolsChain = buildChain({ firstResult: null });
const brandingChain = buildChain({ selectResult: [] });
const slugChain = buildChain({ firstResult: null }); const slugChain = buildChain({ firstResult: null });
const insertChain = buildChain({ returningResult: [{ id: 51 }] }); const insertChain = buildChain({ returningResult: [{ id: 51 }] });
const feedbackInsertChain = buildChain(); const feedbackInsertChain = buildChain();
db.__setImplementations(settingChain, slugChain, insertChain, feedbackInsertChain); db.__setImplementations(
feedbackProbe, devtoolsChain, brandingChain, slugChain, insertChain, feedbackInsertChain
);
await request(buildApp()) await request(buildApp())
.post('/events') .post('/events')
.send(BASE_BODY) .send(BASE_BODY)
.expect(201); .expect(201);
expect(db).toHaveBeenNthCalledWith(4, 'event_feedback_settings'); expect(db).toHaveBeenNthCalledWith(6, 'event_feedback_settings');
expect(feedbackInsertChain.insert).toHaveBeenCalledTimes(1); expect(feedbackInsertChain.insert).toHaveBeenCalledTimes(1);
}); });
it('does NOT create a feedback row when global setting is unset and body omits feedback_enabled', async () => { it('does NOT create a feedback row when global setting is unset and body omits feedback_enabled', async () => {
const settingChain = buildChain({ firstResult: null });
const slugChain = buildChain({ firstResult: null }); const slugChain = buildChain({ firstResult: null });
const insertChain = buildChain({ returningResult: [{ id: 52 }] }); const insertChain = buildChain({ returningResult: [{ id: 52 }] });
db.__setImplementations(settingChain, slugChain, insertChain); db.__setImplementations(...baseSettingsChains(), slugChain, insertChain);
await request(buildApp()) await request(buildApp())
.post('/events') .post('/events')
.send(BASE_BODY) .send(BASE_BODY)
.expect(201); .expect(201);
// Only 3 db() calls — the event_feedback_settings table is never // 5 db() calls: feedback + devtools + branding probes, slug, insert.
// touched because feedback_enabled resolved to false. // event_feedback_settings is never touched.
expect(db).toHaveBeenCalledTimes(3); expect(db).toHaveBeenCalledTimes(5);
expect(db).not.toHaveBeenCalledWith('event_feedback_settings'); expect(db).not.toHaveBeenCalledWith('event_feedback_settings');
}); });