Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions cypress/tests/e2e/isr-cache-headers.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* ISR cache-header tests — asserts that ISR pages return Cache-Control with
* s-maxage=300 (PR #1825) and that a second request is served from disk cache
* rather than regenerated (PR #1824: isrMemoryCacheSize:0 uses disk cache).
*
* IMPORTANT: ISR only activates in production mode. Run against a production
* build, not the dev server:
*
* npm run build
* npm run start
* cypress run --spec cypress/tests/e2e/isr-cache-headers.cy.js
*
* The page IDs below (940, 186734, 1, 180Earth) are real records in the
* production/staging API. These tests will fail against a dev environment
* that does not have these records. In that case, replace the IDs with ones
* that exist in your target environment.
*/

const ISR_PAGES = [
'/planters/940',
'/trees/186734',
'/organizations/1',
'/wallets/180Earth',
'/top',
];

describe('ISR Cache-Control headers reflect 300s revalidation (PR #1825)', () => {
ISR_PAGES.forEach((path) => {
it(`${path} — Cache-Control includes s-maxage=300 (or NEXT_CACHE_REVALIDATION_OVERRIDE if set)`, () => {
const expected = Cypress.env('NEXT_CACHE_REVALIDATION_OVERRIDE') || 300;
cy.request(path).then((response) => {
expect(response.status).to.eq(200);
expect(response.headers['cache-control']).to.match(
new RegExp(`s-maxage=${expected}`),
);
});
});
});
});

describe('ISR disk cache is active after isrMemoryCacheSize:0 (PR #1824)', () => {
ISR_PAGES.forEach((path) => {
it(`${path} — second request served from disk cache (HIT or STALE)`, () => {
cy.request(path);
cy.request(path).then((response) => {
expect(response.status).to.eq(200);
// HIT = page was in the cache window, served from disk
// STALE = beyond the window, served stale while regen runs in background
// Either confirms disk cache is working; MISS would mean caching is broken
expect(response.headers['x-nextjs-cache']).to.match(/HIT|STALE/);
});
});
});
});
127 changes: 127 additions & 0 deletions cypress/tests/integration/isr-regression.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* ISR page regression tests — verifies that all 6 ISR pages still render
* correctly after the isrMemoryCacheSize:0 change in next.config.js (PR #1824)
* and the revalidate interval change from 30s to 300s (PR #1825).
*
* Run with: npm run cypress:run:fast
* Requires: CYPRESS_nock=true (set by cypress:run:fast via --env nock=true)
*/

import { prepareNocks, clearNocks } from './nockRoutes';
import leaders from '../../../doc/examples/countries/leader.json';
import org from '../../../doc/examples/organizations/1.json';
import planter from '../../../doc/examples/planters/940.json';
import wallet from '../../../doc/examples/wallets/180Earth.json';

beforeEach(() => {
clearNocks();
});

describe('ISR pages render after isrMemoryCacheSize:0 (PR #1824 regression)', () => {
it('renders planter page', () => {
prepareNocks({ planter });
cy.visit(`/planters/${planter.id}`, { failOnStatusCode: false });
cy.get('.MuiTypography-h2').contains(/sebastian g/i);
});

it('renders tree page', () => {
cy.fixture('images/trees/1.jpg').then((image) => {
const blob = Cypress.Blob.base64StringToBlob(image, 'image/jpg');
const url = Cypress.Blob.createObjectURL(blob);
cy.fixture('tree186734').then((tree) => {
prepareNocks({ tree: { ...tree, image_url: url } });
cy.visit(`/trees/${tree.id}`, { failOnStatusCode: false });
cy.contains(`${tree.id}`);
});
});
});

it('renders organization page', () => {
cy.fixture('images/organization.png').then((image) => {
const blob = Cypress.Blob.base64StringToBlob(image, 'images/png');
const photo_url = Cypress.Blob.createObjectURL(blob);
prepareNocks({ organization: { ...org, photo_url } });
});
cy.visit(`/organizations/${org.id}`, { failOnStatusCode: false });
cy.url().should('include', '/organizations');
cy.contains(org.name);
});

it('renders wallet page', () => {
prepareNocks({ wallet });
cy.task('nockIntercept', {
hostname: 'https://dev-k8s.treetracker.org',
method: 'get',
path: `/query/tokens?wallet=${wallet.id}`,
statusCode: 200,
body: { total: 0, offset: 0, limit: 20, tokens: [] },
});
cy.task('nockIntercept', {
hostname: 'https://dev-k8s.treetracker.org',
method: 'get',
path: `/query/wallets/${wallet.id}/token-region-count`,
statusCode: 200,
body: { walletStatistics: [] },
});
cy.task('nockIntercept', {
hostname: 'https://dev-k8s.treetracker.org',
method: 'get',
path: `/query/wallets/${wallet.id}`,
statusCode: 200,
body: wallet,
});
cy.task('nockIntercept', {
hostname: 'https://dev-k8s.treetracker.org',
method: 'get',
path: `/query/species?wallet_id=${wallet.id}`,
statusCode: 200,
body: { total: null, offset: 0, limit: 20, species: [] },
});
cy.task('nockIntercept', {
hostname: 'https://dev-k8s.treetracker.org',
method: 'get',
path: `/query/trees?wallet_id=${wallet.id}`,
statusCode: 200,
body: { total: 0, offset: 0, limit: 20, trees: [] },
});
cy.visit(`/wallets/${wallet.id}`, { failOnStatusCode: false });
cy.get('.MuiTypography-h2')
.eq(0)
.contains(/Maynard.Stroman79/i);
});

it('renders top page', () => {
prepareNocks({});
cy.task('nockIntercept', {
hostname: 'https://dev-k8s.treetracker.org',
method: 'get',
path: '/query/organizations/featured',
statusCode: 200,
body: { organizations: [org] },
});
cy.task('nockIntercept', {
hostname: 'https://dev-k8s.treetracker.org',
method: 'get',
path: '/query/planters/featured',
statusCode: 200,
body: { planters: [planter] },
});
cy.task('nockIntercept', {
hostname: 'https://dev-k8s.treetracker.org',
method: 'get',
path: '/query/wallets/featured',
statusCode: 200,
body: { wallets: [wallet] },
});
cy.intercept('GET', '**/countries/**', { statusCode: 200, body: leaders });
cy.visit('/top', { failOnStatusCode: false });
cy.contains('Featured trees');
cy.contains('Tanzania');
});

// TODO: v2/captures/[captureid] — no fixture exists yet for the captures API
// (getCapturesById, getGrowerById, getStakeHolderById, getCountryByLatLon).
// Add a fixture under doc/examples/captures/ and a nockIntercept route here
// before enabling this test.
it.skip('renders captures page', () => {});
});
9 changes: 9 additions & 0 deletions deployment/overlays/prod/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,12 @@ spec:
value: 'false'
- name: NEXT_PUBLIC_GA_ID
value: G-6QS615H24Z
- name: NODE_OPTIONS
value: "--max-old-space-size=1344"
resources:
requests:
memory: "1300Mi"
cpu: "50m"
limits:
memory: "1792Mi"
cpu: "200m"
3 changes: 3 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const withImages = require('next-images');

module.exports = {
...withImages(),
experimental: {
isrMemoryCacheSize: 0,
},
images: {
domains: [
'treetracker-production-images.s3.eu-central-1.amazonaws.com',
Expand Down
2 changes: 1 addition & 1 deletion src/pages/organizations/[organizationid].js
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ const getStaticProps = wrapper(async ({ params }) => {
const props = await serverSideData(params);
return {
props,
revalidate: Number(process.env.NEXT_CACHE_REVALIDATION_OVERRIDE) || 30,
revalidate: Number(process.env.NEXT_CACHE_REVALIDATION_OVERRIDE) || 300,
};
});

Expand Down
2 changes: 1 addition & 1 deletion src/pages/planters/[planterid].js
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ const getStaticProps = wrapper(async ({ params }) => {
const props = await serverSideData(params);
return {
props,
revalidate: Number(process.env.NEXT_CACHE_REVALIDATION_OVERRIDE) || 30,
revalidate: Number(process.env.NEXT_CACHE_REVALIDATION_OVERRIDE) || 300,
};
});

Expand Down
2 changes: 1 addition & 1 deletion src/pages/top.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ const getStaticProps = utils.wrapper(async ({ params }) => {
const props = await serverSideData(params);
return {
props,
revalidate: Number(process.env.NEXT_CACHE_REVALIDATION_OVERRIDE) || 30,
revalidate: Number(process.env.NEXT_CACHE_REVALIDATION_OVERRIDE) || 300,
};
});

Expand Down
2 changes: 1 addition & 1 deletion src/pages/trees/[treeid].js
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ const getStaticProps = utils.wrapper(async ({ params }) => {
const props = await serverSideData(params);
return {
props,
revalidate: Number(process.env.NEXT_CACHE_REVALIDATION_OVERRIDE) || 30,
revalidate: Number(process.env.NEXT_CACHE_REVALIDATION_OVERRIDE) || 300,
};
});

Expand Down
2 changes: 1 addition & 1 deletion src/pages/v2/captures/[captureid].js
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ const getStaticProps = utils.wrapper(async ({ params }) => {
const props = await serverSideData(params);
return {
props,
revalidate: Number(process.env.NEXT_CACHE_REVALIDATION_OVERRIDE) || 30,
revalidate: Number(process.env.NEXT_CACHE_REVALIDATION_OVERRIDE) || 300,
};
});

Expand Down
2 changes: 1 addition & 1 deletion src/pages/wallets/[walletid].js
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ const getStaticProps = wrapper(async ({ params }) => {
const props = await serverSideData(params);
return {
props,
revalidate: Number(process.env.NEXT_CACHE_REVALIDATION_OVERRIDE) || 30,
revalidate: Number(process.env.NEXT_CACHE_REVALIDATION_OVERRIDE) || 300,
};
});

Expand Down
Loading