Skip to content

test: cover getPreviewData handler and config, fix ESM jest setup (VP-2467) - #19

Merged
contentstackMridul merged 3 commits into
stagfrom
shopify-lp-test-coverage
Aug 5, 2026
Merged

test: cover getPreviewData handler and config, fix ESM jest setup (VP-2467)#19
contentstackMridul merged 3 commits into
stagfrom
shopify-lp-test-coverage

Conversation

@contentstackMridul

@contentstackMridul contentstackMridul commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Brings the middleware's live code under test and fixes the jest setup that was blocking it. The suite goes from 3 files and 15 tests to 6 files and 41 tests, coverage runs for the first time, and the four live source files land at 94% of lines. One production change: src/index.ts splits into a buildServer() factory in src/app.ts plus a listen shim, because a test could not reach a route without binding a port.

Type of Change

  • feat: new feature
  • fix: bug fix
  • chore: maintenance / dependency update
  • refactor: code improvement without behavior change
  • test: adding or updating tests
  • docs: documentation only

Changes Made

  • src/app.ts (new): buildServer() builds a fully configured fastify instance without binding a port. Plugin registration order, logger config and route wiring are lifted verbatim from index.ts; the one addition is a logger option so tests can silence output.
  • src/index.ts: down to importing buildServer and the existing start(). 59 lines removed, 3 added.
  • tsconfig.test.json: test module moved from CommonJS to ESNext.
  • jest.config.cjs: ts-jest/presets/default-esm, extensionsToTreatAsEsm, useESM: true, plus a moduleNameMapper that strips the ESM-required .js suffix so ../src/app.js resolves to app.ts. src/index.ts added to the coverage ignore list now that it is a shim.
  • package.json: test scripts run jest through node --experimental-vm-modules; test-exclude: ^7.0.1 added to overrides; test:github dropped along with the suite it pointed at.
  • tests/setup.ts: sets the four Contentstack credential env vars, because controllers/index.ts constructs a ContentstackService at import time and that constructor throws on an empty credential.
  • tests/getPreviewData.test.ts (new, 23 tests): happy path, the product-metafields and metaobjects enrichment branches, a regression case pinning that the CDA schema array reaches createContentTypeKeyBased unwrapped, nine schema-layer 400s, render and fetch 500s, a path-traversal attempt run against the real liquid engine, and the unvalidated locale pass-through.
  • tests/config.test.ts: rewritten, 4 tests to 7, now covering the default test env only.
  • tests/config.defaults.test.ts, config.rateLimit.test.ts, config.invalidNumbers.test.ts, config.oversizedRateLimit.test.ts (new, 11 tests): one env permutation each.
  • tests/environment.test.ts and tests/githubSync.test.ts deleted (11 tests).

Coverage, % Stmts as printed by npm test -- --coverage:

File Base This PR
src/controllers/index.ts not measured 96.96%
src/config.ts not measured 100%
src/app.ts did not exist 90%
src/routes/index.ts not measured 83.33%
src/controllers/githubSyncController.ts not measured 3.84% (dead, see notes)
src/controllers/viewsHealthController.ts not measured 1.56% (dead, see notes)
All files not measured 35%

Live files only, by line from coverage/lcov.info with the two dead controllers excluded: 94.00% (47 of 50 lines).

Why These Changes

Three things were in the way, and each had to go before a single useful test could run.

tsconfig.test.json compiled tests as CommonJS, but controllers/index.ts uses import.meta.url, which is a syntax error under CommonJS. The controller that owns the whole preview path could not be imported by a test.

The old src/index.ts built the fastify instance and called listen() in the same module, so importing it to reach a route meant binding port 3002. Extracting buildServer() makes every route reachable through fastify.inject() with no listener running.

--coverage was broken. The pre-existing minimatch: ^9 override (it is there for a Snyk fix and it stays) leaves jest resolving test-exclude@6, which requires minimatch as a callable, and minimatch 9's CJS entry is an object. Overriding test-exclude to ^7 lines the two back up.

What was there before was worse than nothing. tests/environment.test.ts and tests/githubSync.test.ts did not import a single production module between them: they asserted on literals pasted into the test file, so they passed no matter what the source did. Both are deleted. tests/config.test.ts did import src/config, so it survives, rewritten and extended.

JIRA: VP-2467

Breaking Changes

No. The public surface is unchanged: same routes, same plugin order, same logger transport, same listen() call. buildServer() is additive, and test-exclude is a jest-only transitive dependency so the new override has no runtime effect. Verified by booting the built output, see step 5 below.

Testing

  • Unit tests added / updated
  • E2E tests added / updated (if applicable)
  • Manually tested

How to Test

  1. git checkout shopify-lp-test-coverage && npm install
  2. npm test and expect 6 suites, 41 tests passing.
  3. npm test -- --coverage and expect the per-file numbers above. The same command on stag does not produce a report.
  4. npm run build, clean.
  5. Boot check for the bootstrap move: npm start, then GET /health should return {"status":"ok",...} and /documentation/ should serve the swagger UI. Both verified locally against dist/index.js on this branch.
  6. npm run lint fails on this branch and on stag alike: no ESLint config is tracked anywhere in the repo. Not introduced here.

Risk Level: MEDIUM

One production file changed, and it is the service bootstrap. The move is behaviour-preserving by inspection and by the boot check in step 5, but it is the path every deploy takes.

Notes for Reviewer

The 35% overall number is misleading, so read the per-file column instead. githubSyncController.ts and viewsHealthController.ts are 90 of the 140 measured lines and both are dead code, pending team confirmation that nobody uses /sync-github-repo or /health/views as a manual escape hatch. Step 12 of the plan deletes them. Excluding those two files, the live code is at 94%.

security-sca is red and it is not this PR. That gate is a whole-tree --fail-on=all policy check: 37 highs against a ceiling of 25, driven by fast-uri@3.1.2 (10 of the 11 SLA breaches) and brace-expansion@2.0.3. The same workflow ran on stag one minute earlier and reported the identical 37, and fast-uri resolves to 3.1.2 on both sides of this diff. All three delta Snyk checks pass.

Two uncovered lines are worth knowing about, though I would not fix either here. controllers/index.ts:73, the typeof liquid_path !== 'string' guard that returns 400, is unreachable: fastify's Ajv coerceTypes default turns a numeric liquid_path into a string before the handler sees it, and there is a test documenting that. routes/index.ts:20, the /health handler body, has no test.

The config suite is split across five files deliberately. config.ts reads process.env once at import, and native ESM has no module registry reset, so one env permutation per file is the only isolation boundary available. Each file names the permutation it owns in a header comment.

Several config tests pin behaviour I would not call correct: an unparseable PORT yields NaN because there is no || 3002 guard the way rateLimit.max has one, a negative RATE_LIMIT_MAX passes straight through because -5 is truthy, and there is no upper clamp. Each of those is marked "documented, not endorsed" in a comment so a later fix does not read the assertion as a contract.

Nothing runs this suite in CI. Neither workflow has a test job, so npm test is local-only. Worth a follow-up.

The utils half of VP-2467 is contentstack/shopify-live-preview-sdk#27. It is independent of this one: no SDK version bump here, and these tests run against the published SDK.

Implements steps 1-11 of the shopify-lp-test-coverage plan (live code only).

- extract buildServer() into src/app.ts so routes are reachable through
  fastify.inject() without binding a port; index.ts becomes a listen shim
- move jest to native ESM (ts-jest default-esm + --experimental-vm-modules)
  and tsconfig.test module to ESNext, which unblocks import.meta.url in
  controllers/index.ts
- pin minimatch, @fastify/static and test-exclude via overrides so
  test-exclude v7 resolves and coverage runs for the first time
- getPreviewData handler: happy path, product-metafields and metaobjects
  branches, schema 400s, render and fetch 500s, path traversal (0 -> 96.96%)
- config: five env permutation files, one module lifetime each (0 -> 100%)
- delete tests/environment.test.ts and tests/githubSync.test.ts, which
  asserted on inlined literals and imported no production code at all

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@contentstackMridul
contentstackMridul requested a review from a team as a code owner August 4, 2026 07:54
@snyk-io

snyk-io Bot commented Aug 4, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

🔒 Security Scan Results

ℹ️ Note: Only vulnerabilities with available fixes (upgrades or patches) are counted toward thresholds.

Check Type Count (with fixes) Without fixes Threshold Result
🔴 Critical Severity 0 0 10 ✅ Passed
🟠 High Severity 37 0 25 ❌ Failed
🟡 Medium Severity 13 0 500 ✅ Passed
🔵 Low Severity 0 0 1000 ✅ Passed

⏱️ SLA Breach Summary

⚠️ Warning: The following vulnerabilities have exceeded their SLA thresholds (days since publication).

Severity Breaches (with fixes) Breaches (no fixes) SLA Threshold (with/no fixes) Status
🔴 Critical 0 0 15 / 30 days ✅ Passed
🟠 High 11 0 30 / 120 days ❌ Failed
🟡 Medium 0 0 90 / 365 days ✅ Passed
🔵 Low 0 0 180 / 365 days ✅ Passed

🟠 High Severity - SLA Breached Issues (with fixes)

Showing 11 issue(s) that have exceeded the 30-day SLA threshold:

  1. Interpretation Conflict

    • ID: SNYK-JS-FASTURI-17675102
    • Package: fast-uri@3.1.2
    • Published: 35 days ago (SLA: 30 days)
    • CVSS Score: 8.7
    • CVE: CVE-2026-13676
  2. Interpretation Conflict

    • ID: SNYK-JS-FASTURI-17675102
    • Package: fast-uri@3.1.2
    • Published: 35 days ago (SLA: 30 days)
    • CVSS Score: 8.7
    • CVE: CVE-2026-13676
  3. Interpretation Conflict

    • ID: SNYK-JS-FASTURI-17675102
    • Package: fast-uri@3.1.2
    • Published: 35 days ago (SLA: 30 days)
    • CVSS Score: 8.7
    • CVE: CVE-2026-13676
  4. Interpretation Conflict

    • ID: SNYK-JS-FASTURI-17675102
    • Package: fast-uri@3.1.2
    • Published: 35 days ago (SLA: 30 days)
    • CVSS Score: 8.7
    • CVE: CVE-2026-13676
  5. Interpretation Conflict

    • ID: SNYK-JS-FASTURI-17675102
    • Package: fast-uri@3.1.2
    • Published: 35 days ago (SLA: 30 days)
    • CVSS Score: 8.7
    • CVE: CVE-2026-13676
  6. Interpretation Conflict

    • ID: SNYK-JS-FASTURI-17675102
    • Package: fast-uri@3.1.2
    • Published: 35 days ago (SLA: 30 days)
    • CVSS Score: 8.7
    • CVE: CVE-2026-13676
  7. Interpretation Conflict

    • ID: SNYK-JS-FASTURI-17675102
    • Package: fast-uri@3.1.2
    • Published: 35 days ago (SLA: 30 days)
    • CVSS Score: 8.7
    • CVE: CVE-2026-13676
  8. Interpretation Conflict

    • ID: SNYK-JS-FASTURI-17675102
    • Package: fast-uri@3.1.2
    • Published: 35 days ago (SLA: 30 days)
    • CVSS Score: 8.7
    • CVE: CVE-2026-13676
  9. Interpretation Conflict

    • ID: SNYK-JS-FASTURI-17675102
    • Package: fast-uri@3.1.2
    • Published: 35 days ago (SLA: 30 days)
    • CVSS Score: 8.7
    • CVE: CVE-2026-13676
  10. Interpretation Conflict

  • ID: SNYK-JS-FASTURI-17675102
  • Package: fast-uri@3.1.2
  • Published: 35 days ago (SLA: 30 days)
  • CVSS Score: 8.7
  • CVE: CVE-2026-13676
  1. Inefficient Algorithmic Complexity
  • ID: SNYK-JS-BRACEEXPANSION-17706650
  • Package: brace-expansion@2.0.3
  • Published: 34 days ago (SLA: 30 days)
  • CVSS Score: 8.7
  • CVE: CVE-2026-13149

❌ BUILD FAILED - Security checks failed

Please review and fix the security vulnerabilities before merging.

@contentstackMridul contentstackMridul changed the title test: cover getPreviewData handler and config, fix ESM jest setup test: cover getPreviewData handler and config, fix ESM jest setup (VP-2467) Aug 4, 2026
Conflict was package.json only, in the overrides block.

stag (PR #20, the Snyk fix) deleted overrides entirely: the exact
@fastify/static 9.1.1 pin was what held it at a vulnerable version, and
swagger-ui ^6.1.1 brings @fastify/static 10 on its own. This branch had
added test-exclude ^7.0.1 to that same block.

Took stag's side. The test-exclude override existed only to work around
the minimatch ^9 pin, which starved test-exclude@6 of a callable
minimatch. With the pin gone, test-exclude@6 resolves its own
minimatch@3 and --coverage works unaided, so the override is dead weight
rather than something to carry forward. Scripts keep this branch's
--experimental-vm-modules form; deps take stag's swagger-ui ^6.1.1.

package-lock.json reset to stag verbatim; npm install reports up to date,
confirming this branch introduced no dependency of its own.

Verified on the merge result: 6 suites / 41 tests pass, --coverage
reports the same per-file numbers as before the merge (controllers/index
96.96%, config 100%, app 90%, routes 83.33%), tsc build clean, and a boot
check of dist/index.js serves /health, /documentation and all seven
swagger assets under @fastify/static 10. Plugin registration order in
app.ts still matches stag's index.ts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

🔒 Security Scan Results

ℹ️ Note: Only vulnerabilities with available fixes (upgrades or patches) are counted toward thresholds.

Check Type Count (with fixes) Without fixes Threshold Result
🔴 Critical Severity 0 0 10 ✅ Passed
🟠 High Severity 0 0 25 ✅ Passed
🟡 Medium Severity 0 0 500 ✅ Passed
🔵 Low Severity 0 0 1000 ✅ Passed

⏱️ SLA Breach Summary

✅ No SLA breaches detected. All vulnerabilities are within acceptable time thresholds.

Severity Breaches (with fixes) Breaches (no fixes) SLA Threshold (with/no fixes) Status
🔴 Critical 0 0 15 / 30 days ✅ Passed
🟠 High 0 0 30 / 120 days ✅ Passed
🟡 Medium 0 0 90 / 365 days ✅ Passed
🔵 Low 0 0 180 / 365 days ✅ Passed

✅ BUILD PASSED - All security checks passed

Comment thread tests/config.defaults.test.ts Outdated

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we testing config file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Config testing was deliberate: this middleware is customer facing, and nearly everything about its runtime behaviour comes from configuration (port, host, Contentstack credentials, rate limit). Read any of that wrong and the service either won't start or misbehaves in a way that's hard to trace back.

But you're right that five files to test one config file wasn't justified. Sorted in f354933: it's now a single tests/config.test.ts with one describe block per scenario. Five files down to one, same 18 assertions, config.ts still at 100%.

@kirtesh-cstk kirtesh-cstk 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.

LGTM, just check that one comment

Reviewer point on PR #19: config was split across five test files
(config.test, config.defaults, config.invalidNumbers, config.oversizedRateLimit,
config.rateLimit), one per env permutation.

The comments in those files justified the split by claiming native ESM has no
module registry reset, so a fresh config.ts evaluation was only obtainable by
starting a new test file. That premise is wrong. jest.resetModules() works fine
here — under native ESM the `jest` object simply is not injected as a global, so
the original attempt would have died on "jest is not defined". Importing it from
'@jest/globals' fixes that, and resetModules() + a dynamic import re-runs
config.ts against whatever env the case installed.

So all five collapse into tests/config.test.ts with one describe per permutation,
behind a loadConfig() helper. Same 18 assertions, no coverage change:
config.ts stays at 100%, All files stays at 35%.

Also refreshed tests/README.md, which still documented githubSync.test.ts,
environment.test.ts and `npm run test:github` — all removed earlier in this PR —
and dropped two empty leftover dirs (tests/controllers, tests/routes).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

🔒 Security Scan Results

ℹ️ Note: Only vulnerabilities with available fixes (upgrades or patches) are counted toward thresholds.

Check Type Count (with fixes) Without fixes Threshold Result
🔴 Critical Severity 0 0 10 ✅ Passed
🟠 High Severity 0 0 25 ✅ Passed
🟡 Medium Severity 0 0 500 ✅ Passed
🔵 Low Severity 0 0 1000 ✅ Passed

⏱️ SLA Breach Summary

✅ No SLA breaches detected. All vulnerabilities are within acceptable time thresholds.

Severity Breaches (with fixes) Breaches (no fixes) SLA Threshold (with/no fixes) Status
🔴 Critical 0 0 15 / 30 days ✅ Passed
🟠 High 0 0 30 / 120 days ✅ Passed
🟡 Medium 0 0 90 / 365 days ✅ Passed
🔵 Low 0 0 180 / 365 days ✅ Passed

✅ BUILD PASSED - All security checks passed

@contentstackMridul
contentstackMridul merged commit aee4ed3 into stag Aug 5, 2026
6 checks passed
@contentstackMridul
contentstackMridul deleted the shopify-lp-test-coverage branch August 5, 2026 11:26
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.

2 participants