Skip to content

Fail configured builds on Firestore read failure (#115) - #119

Merged
spizeck merged 2 commits into
mainfrom
fix/issue-115-firestore-build-reads
Sep 23, 2026
Merged

spizeck merged 2 commits into
mainfrom
fix/issue-115-firestore-build-reads

Conversation

@spizeck

@spizeck spizeck commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

Issue #104 hardened beer slug enumeration (getBeerStaticParams) against the Firebase Web SDK's silent offline-cache fallback, but three public build-time read paths still used plain getDocs(). On a configured build where Firestore is unreachable or denies the read, getDocs() resolves an empty offline cache — indistinguishable from a legitimately empty collection — so the build would happily deploy an empty catalog, an empty venue list, altered /where-to-buy FAQ copy, beer URLs missing from sitemap.xml, and legitimate beer detail pages as 404s until the next rebuild.

This PR extends the established #104 contract to every public build-time read:

  • Unconfigured build (CI, local without .env.local): each helper returns its empty fallback ([] or null) without initializing Firebase — credential-free builds remain intentional, and now produce no SDK warnings at all.
  • Configured build: each helper reads via getDocsFromServer(), which never resolves from the offline cache. A genuinely empty collection is still a valid successful result; a real backend/network/permission failure rejects and fails the build.

Query semantics, ordering, filtering, mapping, and return types are unchanged. getBeers() and getBeerStaticParams() continue to share publicBeersQuery() so catalog enumeration cannot drift.

Closes #115

Changes

  • lib/beers.ts: getBeers() and getBeerBySlug() now guard on hasFirebaseConfig() and read via getDocsFromServer(); getDocs import removed. JSDoc updated to state the shared contract; getBeerBySlug keeps snapshot.empty → null → notFound() for genuinely absent slugs.
  • lib/venues.ts: getVenues() gets the same guard + authoritative read; getDocs import removed.
  • tests/lib/firestore-build-reads.test.ts (renamed from beers-static-params.test.ts): expanded from 7 to 17 tests covering all four helpers — unconfigured fallbacks that assert no Firebase app is ever initialized, configured-failure rejections against a real (nonexistent-project) backend, and per-function source guards that fail if anyone reintroduces the cache-fallback getDocs call or drops the guard/mapping.
  • docs/TECHNICAL.md + .github/workflows/ci.yml comment: updated to describe the uniform contract (unconfigured → deliberate empty fallback; configured success → authoritative data; configured failure → build failure).

Verification

  • npm ci
  • npm run check:react-versions
  • npx tsc --noEmit
  • npm run lint
  • npm test — 329 tests / 103 suites, all pass
  • npm run test:rules (needs Java 21+) — 29 tests / 7 suites, all pass
  • npm run build — see below
  • npm run test:smoke — 116 Playwright tests pass against the production build
  • npm run check:md-links
  • npm audit --omit=dev — 0 vulnerabilities

Build contract verified end-to-end on this branch:

Scenario Result
Credential-free (CI simulation, env emptied) Build succeeds, 22 routes, /beers/[slug] emits zero params, zero Firebase warnings (previously INVALID_ARGUMENT noise)
Configured, Firestore reachable (.env.local) Build succeeds, 31 routes, 9 real beer detail pages generated
Configured, Firestore broken (nonexistent project) next build exits 1 — Failed to collect page data for /beers/[slug], code: 'unavailable'

Smoke suite also confirms unknown beer slugs still render the intended 404 (/beers/definitely-not-a-real-beer axe test).

Risk / deployment notes

  • Production behavior change is intentional: a Vercel build whose Firestore reads fail now fails the deploy instead of shipping degraded pages. A legitimate empty beers/venues collection still deploys fine.
  • No rules changes, no new env vars, no runtime/ISR strategy changes; admin dashboard reads (components/admin-dashboard.tsx) are authenticated browser reads and intentionally untouched.
  • No secrets, credentials, or private data were committed.

Generated with Devin

Summary by Sourcery

Make public Firestore reads fail configured builds on backend errors while retaining credential-free empty fallbacks for unconfigured builds.

Bug Fixes:

  • Prevent configured builds from silently deploying empty catalogs, venue lists, altered FAQ content, missing sitemap entries, or false beer-page 404s when Firestore reads fail.
  • Preserve intentional empty fallbacks for builds without Firebase configuration while avoiding Firebase initialization and SDK warnings.

Enhancements:

  • Standardize all public build-time Firestore reads on authoritative server reads that distinguish genuine empty results from backend failures.
  • Keep query behavior, mappings, ordering, filtering, and missing-slug 404 behavior unchanged while making configured read failures fail the build.

Documentation:

  • Document the uniform configured and unconfigured Firestore build-read contract.

Tests:

  • Expand build-read coverage to validate unconfigured fallbacks, configured failure propagation, Firebase initialization behavior, and protection against cache-fallback reads.

getBeers(), getVenues(), and getBeerBySlug() used plain getDocs(), which
silently resolves from the offline cache when a build-time backend read
fails — indistinguishable from a legitimately empty collection and able
to deploy an empty catalog, empty venue list, altered FAQ copy, or beer
pages as 404s.

Extend the #104 contract to all public build-time reads: unconfigured
builds (CI, local without .env.local) return the empty fallback without
initializing Firebase; configured builds read via getDocsFromServer so a
legitimately empty result stays valid while a real failure throws and
fails the build. Query semantics, ordering, and mapping are unchanged.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@sourcery-ai

sourcery-ai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

All public build-time Firestore reads now skip Firebase entirely when unconfigured and use getDocsFromServer() when configured, causing real backend failures to fail the build while preserving valid empty results and existing page semantics. Tests and documentation were expanded to enforce and explain this contract.

Sequence diagram for configured build-time Firestore reads

sequenceDiagram
    participant Build
    participant Helper
    participant Firebase
    participant Firestore

    Build->>Helper: getBeers() / getVenues() / getBeerBySlug() / getBeerStaticParams()
    Helper->>Firebase: hasFirebaseConfig()
    Firebase-->>Helper: true
    Helper->>Firebase: getFirebaseDb()
    Firebase-->>Helper: Firestore database
    Helper->>Firestore: getDocsFromServer(query)
    alt read succeeds
        Firestore-->>Helper: Snapshot, possibly empty
        Helper-->>Build: Mapped data or null
    else backend or permission failure
        Firestore-->>Helper: Reject with error
        Helper-->>Build: Build failure
    end
Loading

Flow diagram for the build-time Firestore read contract

flowchart TD
    A[Build-time helper called] --> B{"hasFirebaseConfig()"}
    B -->|No| C[Return empty fallback]
    C --> D[Build continues without initializing Firebase]
    B -->|Yes| E[getDocsFromServer]
    E --> F{Firestore read result}
    F -->|Success, including empty collection| G[Map and return data]
    F -->|Backend, network, or permission failure| H[Reject and fail build]
Loading

File-Level Changes

Change Details Files
Standardize all public build-time Firestore reads on explicit configuration gating and authoritative server reads.
  • Return [] or null immediately when Firebase configuration is absent, avoiding SDK initialization and warnings.
  • Use getDocsFromServer() for configured reads so network, permission, and backend failures reject the build instead of falling back to cached emptiness.
  • Preserve existing queries, mappings, ordering, filtering, shared beer enumeration, and absent-slug 404 behavior.
lib/beers.ts
lib/venues.ts
Expand regression coverage for the build-time Firestore read contract.
  • Cover unconfigured fallbacks and verify no Firebase app is initialized.
  • Exercise configured failures against an unreachable project and require unavailable rejection.
  • Add source-level guards for authoritative APIs, configuration checks, mappings, query semantics, and static-route delegation.
tests/lib/firestore-build-reads.test.ts
tests/lib/beers-static-params.test.ts
Document the uniform configured/unconfigured build behavior and update CI guidance.
  • Describe credential-free builds as deliberate empty-fallback builds with no Firebase access.
  • Document that configured read failures fail deployment while legitimate empty collections remain valid.
  • Update the CI build comment to reflect hasFirebaseConfig()-based behavior.
docs/TECHNICAL.md
.github/workflows/ci.yml

Assessment against linked issues

Issue Objective Addressed Explanation
#115 Change all public build-time Firestore reads (getBeers(), getVenues(), and getBeerBySlug(), alongside the previously hardened getBeerStaticParams()) to use authoritative server reads when Firebase is configured, so Firestore failures reject the build instead of silently producing empty catalogs, misleading venue content, or beer 404s. ✅
#115 Preserve intentional credential-free build behavior by returning empty fallbacks without initializing Firebase when no Firebase configuration is present. ✅
#115 Add documentation and tests covering the authoritative-read invariant, unconfigured fallbacks, and rejection on configured Firestore failures for all affected helpers. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@vercel

vercel Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated
deepdivebrewing-web Ready Ready Preview Sep 23, 2026 9:46pm UTC

Request Review

sourcery-ai[bot]
sourcery-ai Bot previously approved these changes Sep 23, 2026

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="tests/lib/firestore-build-reads.test.ts" line_range="62-65" />
<code_context>
+  fn: () => Promise<T>,
+  expected: T
+): Promise<void> {
+  const appsBefore = getApps().length;
+  const result = await withEnv({}, fn);
+  assert.deepEqual(result, expected);
+  assert.equal(getApps().length, appsBefore);
+}
+
</code_context>
<issue_to_address>
**nitpick (testing):** The unconfigured-build test compares the app count with a baseline that can already contain an initialized Firebase app, so later fallback tests pass even if the helper initializes or uses Firebase during the call. This does not reliably verify the documented no-initialization contract for `getVenues`, `getBeerBySlug`, and `getBeerStaticParams`.

**Triggers:** When a preceding configured-path test has initialized Firebase in the same test process.

**Suggested fix:** Terminate and delete Firebase apps before each unconfigured-path assertion, or assert that the helper does not create a new app from a known zero-app baseline.
</issue_to_address>

Sourcery assessment

Approved.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread tests/lib/firestore-build-reads.test.ts Outdated
Group the unconfigured-build tests before the configured-failure tests so
the getApps() assertion proves no Firebase app was ever initialized,
rather than comparing against a nonzero baseline once a configured test
has run. Addresses a Sourcery review nitpick on the PR.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@sourcery-ai
sourcery-ai Bot dismissed their stale review September 23, 2026 21:46

Sourcery withdrew this approval because the latest commits introduced blocking findings.

@spizeck
spizeck merged commit dacfcf7 into main Sep 23, 2026
4 checks passed
@spizeck
spizeck deleted the fix/issue-115-firestore-build-reads branch September 23, 2026 22:36

This branch was successfully deployed

1 active deployment
Preview — fdfa247d Deployed Sep 23, 2026 by vercel[bot]
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.

Build-time Firestore reads can silently deploy an empty catalog and beer 404s

1 participant