{
  "schemaVersion": 3,
  "generatedAt": "2026-08-28T16:43:10.147Z",
  "documentation": "https://www.grumpyoldgamers.net/about",
  "publisher": {
    "name": "Grumpy Old Gamers",
    "legalName": "Axiom Testing Ltd",
    "companyNumber": "17267026",
    "jurisdiction": "England and Wales",
    "url": "https://www.grumpyoldgamers.net/",
    "email": "hello@grumpyoldgamers.net",
    "sameAs": [
      "https://axiomtesting.co.uk/",
      "https://bsky.app/profile/grumpyoldgamers.net"
    ],
    "statement": "Grumpy Old Gamers is owned and operated by Axiom Testing Ltd, a company registered in England and Wales (company number 17267026)."
  },
  "product": {
    "name": "Grumpy Old Gamers",
    "unit": "titles tracked with live prices",
    "count": 23652,
    "note": "One per slug returned by the data service, which is also one per URL in /game/sitemap.xml. Includes DLC, soundtracks and alternate editions: the upstream catalogue does not reliably distinguish them from base games, so this is a count of tracked pages, not of distinct games.",
    "measuredAt": "2026-08-28T16:41:35.825Z",
    "source": "https://api.grumpyoldgamers.net/games/slugs"
  },
  "build": {
    "commit": "fe970779818dbb8422d788f1e3c496a50b017256",
    "commitShort": "fe97077"
  },
  "measurement": {
    "at": "2026-08-28T16:41:33.833Z",
    "commit": "d3a8dc64703419d149341d639ad17714fc1a9ef1",
    "commitShort": "d3a8dc6",
    "isCurrent": false,
    "note": "Coverage is measured in CI after the suite runs, then committed, so the figures can trail the deployed commit by one. isCurrent reports whether they do."
  },
  "tests": {
    "unit": {
      "framework": "vitest",
      "files": 21,
      "tests": 480,
      "passed": 480,
      "failed": 0,
      "gatesDeployment": true
    },
    "coverage": {
      "lines": 71.45,
      "statements": 69.89,
      "functions": 77.57,
      "branches": 70.55,
      "tool": "@vitest/coverage-v8 (v8 provider; every file matching the include scope is counted, tested or not)",
      "scope": {
        "include": [
          "lib/**/*.ts"
        ],
        "exclude": [
          "lib/posthog-server.ts"
        ],
        "statement": "Coverage of the server-side logic in lib/ only — price and currency selection, region resolution, affiliate URL construction, search, category mapping, and the data-service and IsThereAnyDeal response mappers. It does not cover the React components in components/, the App Router pages and API routes in app/, or the node:https bulk-price path and unused legacy helpers in lib/itad.ts, which together account for most of the uncovered lines. The end-to-end and API suites reported alongside this one do exercise components/, app/ and the API routes from the outside, but they are not line-instrumented and contribute nothing to this percentage."
      }
    },
    "buildGate": {
      "command": "npm run build",
      "description": "The build script runs the unit suite before next build. Vercel runs `npm run build`, so a failing unit test blocks the build and cannot be deployed."
    },
    "e2e": {
      "framework": "playwright",
      "files": 8,
      "tests": 92,
      "passed": 89,
      "failed": 0,
      "skipped": 3,
      "flaky": 0,
      "gatesDeployment": false,
      "runsIn": "GitHub Actions, separately from the Vercel build",
      "measuredAt": "2026-08-28T08:46:27.110Z",
      "scope": {
        "statement": "End-to-end journeys through the rendered site, driven by Playwright: the region switcher (including the mobile dropdown-overflow regression reported on 2026-08-24), header and drawer navigation, search submission and results, the homepage sections, game detail pages (title, canonical, Open Graph tags, VideoGame JSON-LD and outbound-link rel safety), and the best-of category pages including sorting. Each spec runs twice: once on desktop Chrome and once on an emulated Pixel viewport. It does not exercise the backend data service directly, and it does not gate deployment — it runs as a separate CI job so that a browser flake cannot block a release. A further Playwright project drives Chrome on a physically attached Android handset over adb; it is opt-in, runs only where a device is present, and its results are not counted here.",
        "projects": [
          "desktop",
          "mobile"
        ]
      }
    },
    "api": {
      "framework": "newman (postman collection v2.1)",
      "collection": "Grumpy Old Gamers — frontend API",
      "requests": 29,
      "assertions": 92,
      "passed": 92,
      "failed": 0,
      "gatesDeployment": false,
      "runsIn": "GitHub Actions, separately from the Vercel build",
      "measuredAt": "2026-08-28T08:46:27.413Z",
      "scope": {
        "statement": "Contract tests for this site's own Next.js API routes and public feeds, run with newman against a deployed build: /api/search, /api/prices/[slug] and /api/prices/batch, /api/deals and /api/deals/search, /api/new-releases, the /api/out affiliate redirect including its unknown-store and open-redirect guards, and the public /coverage.json, sitemap and robots.txt surfaces. Assertions check response shape, status codes, currency and slug formats, documented pagination caps and cache headers — not the correctness of the price data itself, which originates upstream. The backend data service is deliberately out of scope: it has its own API suite, reported separately under components. It does not gate deployment — it runs as a separate CI job."
      }
    }
  },
  "components": [
    {
      "name": "Data service",
      "role": "FastAPI backend owning price ingestion, discovery, and the REST API the site reads",
      "source": "https://api.grumpyoldgamers.net/coverage.json",
      "measuredAt": "2026-08-28T14:25:29.631Z",
      "commit": "c090cb114add8409d08e4a12bf4344d8b8b92691",
      "commitShort": "c090cb1",
      "tests": {
        "framework": "pytest",
        "files": 44,
        "tests": 1054,
        "passed": 1054,
        "failed": 0,
        "gatesDeployment": true
      },
      "coverage": {
        "lines": 97.78,
        "statements": 97.78,
        "branches": 94.31,
        "tool": "pytest-cov (coverage.py)",
        "scope": {
          "statement": "Coverage of the entire application package: the ingestion and pricing logic, the FastAPI route handlers, the application entrypoint and the auth dependency. The scheduled sync workers and one-off maintenance scripts are not measured. The figures come from two suites that run together under one coverage run — unit tests against the services, and an in-process end-to-end suite that drives the real application object over HTTP, so routing, middleware, authentication and the response envelopes are executed rather than mocked. Neither suite touches the network; outbound sockets are blocked for the duration of the run, and the database and the third-party storefronts are the only doubles. coverage.py measures executable lines, so the line and statement figures are the same measurement reported under both names. A further HTTP contract suite (Postman/Newman) runs against the deployed service, including as a smoke test after every deploy; it is not measured by coverage.py and is not counted in the figures above."
        }
      }
    }
  ],
  "provenance": [
    {
      "label": "Price data",
      "value": "IsThereAnyDeal API v3 and the Steam storefront, ingested by a separate data service. No storefront is scraped directly."
    },
    {
      "label": "Price freshness",
      "value": "Game pages revalidate every 24 hours and trigger a background price sync on view. Deals, new releases and top sellers revalidate hourly."
    },
    {
      "label": "Regions priced",
      "value": "8 — GB, US, CA, DE, FR, NL, ES and IT, each in its own currency and number format."
    },
    {
      "label": "Estimated prices",
      "value": "Currency-converted prices are marked as estimated, shown with a ~ prefix, and are never used as the headline lowest price when a real quote exists."
    },
    {
      "label": "Affiliate disclosure",
      "value": "Some storefront links earn commission. Disclosed at https://www.grumpyoldgamers.net/affiliate-disclosure"
    }
  ],
  "featured": [
    {
      "slug": "deals",
      "title": "PC Game Deals",
      "description": "Live discounts across storefronts, sorted by depth of cut.",
      "url": "https://www.grumpyoldgamers.net/deals"
    },
    {
      "slug": "top-sellers",
      "title": "Top Sellers",
      "description": "What is selling now, with the cheapest price for each.",
      "url": "https://www.grumpyoldgamers.net/top-sellers"
    },
    {
      "slug": "new-releases",
      "title": "New Releases",
      "description": "Recently released PC games with live prices.",
      "url": "https://www.grumpyoldgamers.net/new-releases"
    },
    {
      "slug": "best/cheap-pc-games",
      "title": "Cheap PC Games",
      "description": "Games under the budget threshold for your region.",
      "url": "https://www.grumpyoldgamers.net/best/cheap-pc-games"
    },
    {
      "slug": "best/best-rpg-games",
      "title": "Best RPG Games",
      "description": "RPGs ranked by current lowest price.",
      "url": "https://www.grumpyoldgamers.net/best/best-rpg-games"
    },
    {
      "slug": "best/best-indie-games",
      "title": "Best Indie Games",
      "description": "Indie games ranked by current lowest price.",
      "url": "https://www.grumpyoldgamers.net/best/best-indie-games"
    }
  ]
}
