Skip to content

Commit 9f7b565

Browse files
Merge pull request #1031 from vally111/feat/fwc26-forecast-paginated-envelope
feat(forecast): return {items,next_cursor,total} paginated envelope f…
2 parents 252b221 + 72bdb73 commit 9f7b565

3 files changed

Lines changed: 627 additions & 470 deletions

File tree

src/routes/__tests__/forecast.test.ts

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1+
/**
2+
* src/routes/__tests__/forecast.test.ts
3+
*
4+
* Legacy test file retained for regression coverage.
5+
* The main pagination-focused suite lives in src/routes/forecast.test.ts.
6+
*/
7+
18
import express from 'express';
29
import request from 'supertest';
310
import { createForecastRouter } from '../forecast.js';
411
import { errorHandler } from '../../middleware/errorHandler.js';
12+
import { FORECAST_DEFAULT_LIMIT } from '../forecast.js';
513

6-
describe('/api/forecast', () => {
7-
it('should return 200 with forecast data', async () => {
14+
describe('/api/forecast — basic route smoke tests', () => {
15+
it('should return 200 with the paginated envelope', async () => {
816
const app = express();
917
app.use('/api/forecast', createForecastRouter(5_000));
1018
app.use(errorHandler);
@@ -13,34 +21,35 @@ describe('/api/forecast', () => {
1321
expect(res.status).toBe(200);
1422
expect(res.body.success).toBe(true);
1523
expect(res.body.data).toBeDefined();
16-
expect(res.body.data.forecast).toBeDefined();
17-
expect(Array.isArray(res.body.data.forecast)).toBe(true);
18-
expect(res.body.data.forecast.length).toBe(24);
19-
expect(res.body.data.generatedAt).toBeDefined();
24+
// New shape: items, total, and optional next_cursor
25+
expect(res.body.data.items).toBeDefined();
26+
expect(Array.isArray(res.body.data.items)).toBe(true);
27+
expect(typeof res.body.data.total).toBe('number');
2028
expect(res.body.requestId).toBeDefined();
2129
expect(res.body.timestamp).toBeDefined();
2230
});
2331

24-
it('should return forecast points with timestamp and value', async () => {
32+
it('items contain timestamp and value fields', async () => {
2533
const app = express();
2634
app.use('/api/forecast', createForecastRouter(5_000));
2735

2836
const res = await request(app).get('/api/forecast');
29-
for (const point of res.body.data.forecast) {
37+
for (const point of res.body.data.items) {
3038
expect(point.timestamp).toBeDefined();
3139
expect(typeof point.timestamp).toBe('string');
3240
expect(point.value).toBeDefined();
3341
expect(typeof point.value).toBe('number');
3442
}
3543
});
3644

37-
it('should return 504 when forecast calculation takes too long', async () => {
45+
it('returns 504 when forecast calculation takes too long', async () => {
3846
const app = express();
3947

4048
const router = createForecastRouter(1);
4149
router.get('/slow', (_req, res) => {
4250
const now = Date.now();
4351
while (Date.now() - now < 200) {
52+
/* spin */
4453
}
4554
res.json({ ok: true });
4655
});
@@ -51,15 +60,23 @@ describe('/api/forecast', () => {
5160
expect(res.body.error.code).toBe('GATEWAY_TIMEOUT');
5261
});
5362

54-
it('should generate 24 forecast points', async () => {
63+
it('default page has FORECAST_DEFAULT_LIMIT items', async () => {
64+
const app = express();
65+
app.use('/api/forecast', createForecastRouter(5_000));
66+
67+
const res = await request(app).get('/api/forecast');
68+
expect(res.body.data.items).toHaveLength(FORECAST_DEFAULT_LIMIT);
69+
});
70+
71+
it('total equals 24 (full hourly forecast horizon)', async () => {
5572
const app = express();
5673
app.use('/api/forecast', createForecastRouter(5_000));
5774

5875
const res = await request(app).get('/api/forecast');
59-
expect(res.body.data.forecast).toHaveLength(24);
76+
expect(res.body.data.total).toBe(24);
6077
});
6178

62-
it('should include requestId in response', async () => {
79+
it('includes requestId in response', async () => {
6380
const app = express();
6481
app.use((req, _res, next) => {
6582
req.id = 'test-request-id';
@@ -71,7 +88,7 @@ describe('/api/forecast', () => {
7188
expect(res.body.requestId).toBe('test-request-id');
7289
});
7390

74-
it('should expose forecast as sub-route of /api in the router', async () => {
91+
it('mounts correctly as sub-route of /api', async () => {
7592
const app = express();
7693
app.use('/api', createForecastRouter(5_000));
7794

0 commit comments

Comments
 (0)