style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
* pnpm --filter @familysync/pwa test:e2e
|
||||
*/
|
||||
|
||||
import mysql from 'mysql2/promise'
|
||||
import mysql from 'mysql2/promise';
|
||||
|
||||
export default async function globalSetup(): Promise<void> {
|
||||
// ── Step 0: Fail-closed environment guard (CR-01 — data-loss prevention) ─────
|
||||
@@ -34,42 +34,42 @@ export default async function globalSetup(): Promise<void> {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
throw new Error(
|
||||
'global-setup refused: NODE_ENV=production. The E2E seed TRUNCATEs tables and must never run against production.',
|
||||
)
|
||||
);
|
||||
}
|
||||
if (process.env.DEV_AUTH_BYPASS !== 'true') {
|
||||
throw new Error(
|
||||
'global-setup refused: DEV_AUTH_BYPASS is not "true". The harness only runs against a dev-bypass stack; ' +
|
||||
'refusing to TRUNCATE/seed an unconfirmed database. Export DEV_AUTH_BYPASS=true (and point DB_* at the dev DB) to proceed.',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// ── Step 1: Readiness gate (D-08) ───────────────────────────────────────────
|
||||
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:5173'
|
||||
const deadline = Date.now() + 60_000
|
||||
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:5173';
|
||||
const deadline = Date.now() + 60_000;
|
||||
|
||||
// Use an explicit success flag (WR-02): inferring success from `Date.now() >= deadline`
|
||||
// after the loop can misreport a success that arrived in the final second as a timeout,
|
||||
// because the `await fetch` itself can push the clock past the deadline before the
|
||||
// post-loop check runs.
|
||||
let ready = false
|
||||
let ready = false;
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
const res = await fetch(`${baseURL}/health`)
|
||||
const res = await fetch(`${baseURL}/health`);
|
||||
if (res.ok) {
|
||||
ready = true
|
||||
break
|
||||
ready = true;
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// ECONNREFUSED or network error — stack not ready yet, keep polling
|
||||
}
|
||||
await new Promise<void>((r) => setTimeout(r, 1_000))
|
||||
await new Promise<void>((r) => setTimeout(r, 1_000));
|
||||
}
|
||||
|
||||
if (!ready) {
|
||||
throw new Error(
|
||||
`health check never returned 200 at ${baseURL}/health — is the dev stack up?\n` +
|
||||
`Ensure the API is running with DEV_AUTH_BYPASS=true and the Vite dev server is on ${baseURL}.`,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// ── Step 1b: DEV_AUTH_BYPASS reachability gate (WR-01) ──────────────────────
|
||||
@@ -79,14 +79,14 @@ export default async function globalSetup(): Promise<void> {
|
||||
// API fails loudly IN SETUP with a clear message instead of ~40 confusing spec failures.
|
||||
// redirect:'manual' surfaces the Authelia redirect as an opaque/3xx response instead of
|
||||
// silently following it.
|
||||
const meRes = await fetch(`${baseURL}/api/me`, { redirect: 'manual' })
|
||||
const meRes = await fetch(`${baseURL}/api/me`, { redirect: 'manual' });
|
||||
if (!meRes.ok) {
|
||||
throw new Error(
|
||||
`/api/me did not return 200 (got ${meRes.status} ${meRes.type}) at ${baseURL}/api/me — ` +
|
||||
`the API is reachable but DEV_AUTH_BYPASS is almost certainly NOT set in the API process.\n` +
|
||||
`A 3xx/opaqueredirect here means /api/me is redirecting to Authelia. ` +
|
||||
`Restart the API with DEV_AUTH_BYPASS=true so it serves Dev User 1 without OIDC.`,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// ── Step 2: Reset-and-seed (D-06 deterministic, D-07 in globalSetup) ────────
|
||||
@@ -98,23 +98,23 @@ export default async function globalSetup(): Promise<void> {
|
||||
user: process.env.DB_USER ?? 'familysync',
|
||||
password: process.env.DB_PASSWORD ?? '',
|
||||
database: process.env.DB_NAME ?? 'familysync',
|
||||
})
|
||||
});
|
||||
|
||||
try {
|
||||
// Disable FK checks so TRUNCATE order is unconstrained
|
||||
await conn.execute('SET FOREIGN_KEY_CHECKS=0')
|
||||
await conn.execute('TRUNCATE TABLE list_items')
|
||||
await conn.execute('TRUNCATE TABLE list_shares')
|
||||
await conn.execute('TRUNCATE TABLE lists')
|
||||
await conn.execute('TRUNCATE TABLE calendar_events')
|
||||
await conn.execute('SET FOREIGN_KEY_CHECKS=1')
|
||||
await conn.execute('SET FOREIGN_KEY_CHECKS=0');
|
||||
await conn.execute('TRUNCATE TABLE list_items');
|
||||
await conn.execute('TRUNCATE TABLE list_shares');
|
||||
await conn.execute('TRUNCATE TABLE lists');
|
||||
await conn.execute('TRUNCATE TABLE calendar_events');
|
||||
await conn.execute('SET FOREIGN_KEY_CHECKS=1');
|
||||
|
||||
// CI guard (Pitfall 4): ensure calendar row id=10 exists before inserting events.
|
||||
// INSERT IGNORE is a no-op if the row already exists (dev DB), creates it if not (CI fresh DB).
|
||||
await conn.execute(
|
||||
`INSERT IGNORE INTO calendars (id, user_id, url, display_name, color, is_shared)
|
||||
VALUES (10, 1, 'https://caldav.fastmail.com/dav/calendars/e2e/', 'FamilySync', '#4A90D9', true)`,
|
||||
)
|
||||
);
|
||||
|
||||
// Seed one timed (not all-day) calendar event on shared calendar id=10.
|
||||
// Anchor to NOON TODAY (UTC) — deliberately NOT "tomorrow" (deep-review BL-02):
|
||||
@@ -123,53 +123,59 @@ export default async function globalSetup(): Promise<void> {
|
||||
// and disappears from the rendered grid, making any "seeded event is visible"
|
||||
// assertion date-fragile. Noon-today lands on today's local calendar date in every
|
||||
// project timezone and is always inside the current-month view.
|
||||
const uid = 'e2e-seed-event-001'
|
||||
const _now = new Date()
|
||||
const uid = 'e2e-seed-event-001';
|
||||
const _now = new Date();
|
||||
const futureStart = new Date(
|
||||
Date.UTC(_now.getUTCFullYear(), _now.getUTCMonth(), _now.getUTCDate(), 12, 0, 0),
|
||||
)
|
||||
);
|
||||
// MariaDB TIMESTAMP requires 'YYYY-MM-DD HH:MM:SS' format, not ISO 8601 with 'T'.
|
||||
const futureStartUtc = futureStart
|
||||
.toISOString()
|
||||
.replace('T', ' ')
|
||||
.replace(/\.\d+Z$/, '')
|
||||
.replace(/\.\d+Z$/, '');
|
||||
const rawVevent = [
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'PRODID:-//FamilySync E2E//EN',
|
||||
'BEGIN:VEVENT',
|
||||
`UID:${uid}`,
|
||||
`DTSTART:${futureStart.toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z')}`,
|
||||
`DTEND:${new Date(futureStart.getTime() + 60 * 60 * 1000).toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z')}`,
|
||||
`DTSTART:${futureStart
|
||||
.toISOString()
|
||||
.replace(/[-:]/g, '')
|
||||
.replace(/\.\d+Z$/, 'Z')}`,
|
||||
`DTEND:${new Date(futureStart.getTime() + 60 * 60 * 1000)
|
||||
.toISOString()
|
||||
.replace(/[-:]/g, '')
|
||||
.replace(/\.\d+Z$/, 'Z')}`,
|
||||
'SUMMARY:Seeded Test Event',
|
||||
'END:VEVENT',
|
||||
'END:VCALENDAR',
|
||||
].join('\r\n')
|
||||
].join('\r\n');
|
||||
|
||||
await conn.execute(
|
||||
`INSERT INTO calendar_events
|
||||
(calendar_id, uid, etag, raw_vevent, title, dtstart_utc, all_day, has_rrule)
|
||||
VALUES (10, ?, 'e2e-etag-001', ?, 'Seeded Test Event', ?, false, false)`,
|
||||
[uid, rawVevent, futureStartUtc],
|
||||
)
|
||||
);
|
||||
|
||||
// Seed one shared list owned by user 1 (D-05 populated half).
|
||||
// belt-and-suspenders: seed BOTH owner_id=1 AND a list_shares row for user_id=1
|
||||
// so /api/lists returns the list regardless of whether it filters by owner or by share.
|
||||
const [listResult] = (await conn.execute(
|
||||
`INSERT INTO lists (owner_id, name, is_shared) VALUES (1, 'E2E Grocery List', true)`,
|
||||
)) as mysql.ResultSetHeader[]
|
||||
const listId = listResult.insertId
|
||||
)) as mysql.ResultSetHeader[];
|
||||
const listId = listResult.insertId;
|
||||
|
||||
await conn.execute(`INSERT INTO list_shares (list_id, user_id) VALUES (?, 1)`, [listId])
|
||||
await conn.execute(`INSERT INTO list_shares (list_id, user_id) VALUES (?, 1)`, [listId]);
|
||||
|
||||
// Two active items with fractional-indexing rank strings — appear in the active section.
|
||||
// 'Milk' and 'Eggs' are the stable anchor texts that lists.spec.ts asserts on.
|
||||
await conn.execute(
|
||||
`INSERT INTO list_items (list_id, text, checked, rank) VALUES (?, 'Milk', false, 'a0'), (?, 'Eggs', false, 'a1')`,
|
||||
[listId, listId],
|
||||
)
|
||||
);
|
||||
} finally {
|
||||
await conn.end()
|
||||
await conn.end();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user