From e47616bf9d373d1390d19a063628dda0a35e1ee5 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 8 Jun 2026 13:00:51 +0000 Subject: [PATCH] =?UTF-8?q?fix(web):=20CI=20E2E=20=E2=80=94=20resilient=20?= =?UTF-8?q?blog/RSS=20without=20Supabase=20+=20update=20stale=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dynamic /blog/rss.xml returned 500 in CI (no Supabase env) and the E2E still asserted the old static feed's hand-written items. - /blog, /blog/[slug], and /blog/rss.xml now degrade gracefully (empty feed/ list, HTTP 200) when Supabase is unavailable, instead of throwing. - E2E: assert the always-present channel LogicSRC Blog and a looser xml content-type, dropping the removed static post titles. Verified with `next dev` and no Supabase env (CI conditions): rss/blog/sitemap all return 200. Co-Authored-By: Claude Opus 4.8 --- apps/logicsrc-web/e2e/logicsrc.spec.ts | 7 +++--- .../logicsrc-web/src/app/blog/[slug]/page.tsx | 20 ++++++++++------- apps/logicsrc-web/src/app/blog/page.tsx | 19 ++++++++++------ .../src/app/blog/rss.xml/route.ts | 22 ++++++++++++------- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/apps/logicsrc-web/e2e/logicsrc.spec.ts b/apps/logicsrc-web/e2e/logicsrc.spec.ts index a52fc40..4b6a74d 100644 --- a/apps/logicsrc-web/e2e/logicsrc.spec.ts +++ b/apps/logicsrc-web/e2e/logicsrc.spec.ts @@ -61,8 +61,9 @@ test.describe("LogicSRC PWA", () => { await expect(sitemap.text()).resolves.toContain("https://logicsrc.com/hire-us"); expect(rss.status()).toBe(200); - expect(rss.headers()["content-type"]).toMatch(/(?:application|text)\/xml/); - await expect(rss.text()).resolves.toContain("LogicSRC OpenSpec Compatibility"); - await expect(rss.text()).resolves.toContain("LogicSRC Credential Sharing OpenSpec"); + expect(rss.headers()["content-type"]).toMatch(/xml/); + // Feed is generated from the blog_posts table; the channel header is always + // present even when there are no posts (e.g. CI without Supabase). + await expect(rss.text()).resolves.toContain("LogicSRC Blog"); }); }); diff --git a/apps/logicsrc-web/src/app/blog/[slug]/page.tsx b/apps/logicsrc-web/src/app/blog/[slug]/page.tsx index ba66313..1e48ab9 100644 --- a/apps/logicsrc-web/src/app/blog/[slug]/page.tsx +++ b/apps/logicsrc-web/src/app/blog/[slug]/page.tsx @@ -20,14 +20,18 @@ type PostRow = { const SITE_URL = (process.env.PUBLIC_URL ?? "https://logicsrc.com").replace(/\/$/, ""); async function loadPost(slug: string): Promise { - const supabase = publicClient(); - const { data } = await supabase - .from("blog_posts") - .select("slug, title, excerpt, html, featured_image, published_at, updated_at") - .eq("slug", slug) - .eq("status", "published") - .maybeSingle(); - return (data as PostRow | null) ?? null; + try { + const supabase = publicClient(); + const { data } = await supabase + .from("blog_posts") + .select("slug, title, excerpt, html, featured_image, published_at, updated_at") + .eq("slug", slug) + .eq("status", "published") + .maybeSingle(); + return (data as PostRow | null) ?? null; + } catch { + return null; + } } export async function generateMetadata({ diff --git a/apps/logicsrc-web/src/app/blog/page.tsx b/apps/logicsrc-web/src/app/blog/page.tsx index 8bbdba5..a92e048 100644 --- a/apps/logicsrc-web/src/app/blog/page.tsx +++ b/apps/logicsrc-web/src/app/blog/page.tsx @@ -30,13 +30,18 @@ function formatDate(value: string): string { } export default async function BlogIndex(): Promise { - const supabase = publicClient(); - const { data } = await supabase - .from("blog_posts") - .select("slug, title, excerpt, featured_image, published_at") - .eq("status", "published") - .order("published_at", { ascending: false }); - const posts = (data ?? []) as PostRow[]; + let posts: PostRow[] = []; + try { + const supabase = publicClient(); + const { data } = await supabase + .from("blog_posts") + .select("slug, title, excerpt, featured_image, published_at") + .eq("status", "published") + .order("published_at", { ascending: false }); + posts = (data ?? []) as PostRow[]; + } catch { + posts = []; + } return ( diff --git a/apps/logicsrc-web/src/app/blog/rss.xml/route.ts b/apps/logicsrc-web/src/app/blog/rss.xml/route.ts index d75c226..336862f 100644 --- a/apps/logicsrc-web/src/app/blog/rss.xml/route.ts +++ b/apps/logicsrc-web/src/app/blog/rss.xml/route.ts @@ -24,16 +24,22 @@ function escapeXml(value: string): string { } // GET /blog/rss.xml — RSS 2.0 feed generated from published blog_posts. +// Degrades to an empty (but valid) feed when Supabase is unavailable. export async function GET(): Promise { const base = baseUrl(); - const supabase = publicClient(); - const { data } = await supabase - .from("blog_posts") - .select("slug, title, excerpt, published_at") - .eq("status", "published") - .order("published_at", { ascending: false }) - .limit(50); - const posts = (data ?? []) as PostRow[]; + let posts: PostRow[] = []; + try { + const supabase = publicClient(); + const { data } = await supabase + .from("blog_posts") + .select("slug, title, excerpt, published_at") + .eq("status", "published") + .order("published_at", { ascending: false }) + .limit(50); + posts = (data ?? []) as PostRow[]; + } catch { + posts = []; + } const items = posts .map((post) => {