Skip to content

Commit f47b916

Browse files
authored
fix: GovRequest and Schemas (#33)
* refactor: update govRequest to GovRequest class and adjust related imports * fix train schema * fix: update dataCode property to accept only number or null
1 parent edb472d commit f47b916

6 files changed

Lines changed: 134 additions & 90 deletions

File tree

src/controllers/gov.controller.js

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { govRequest } from '../utils/govRequest.js';
1+
import { GovRequest } from '../utils/govRequest.js';
22

33
/**
44
* Formats a Date object into a string in the format 'DD/MM/YYYY'.
@@ -23,8 +23,8 @@ export async function getLinesByStation(request, reply) {
2323
const { EventDate, OperatorId, StationId } = request.body;
2424
const date = formatDate(new Date(EventDate));
2525
request.log.info('Getting lines by station', { EventDate: date, OperatorId, StationId });
26-
const response = await govRequest.post('/trafficLicensing/GetLines', { EventDate: date, OperatorId, StationId });
27-
return reply.status(200).send({ data: response.data.Data, success: true });
26+
const response = await GovRequest.post('/trafficLicensing/GetLines', { EventDate: date, OperatorId, StationId });
27+
return reply.status(200).send({ data: response.Data, success: true });
2828
} catch (error) {
2929
request.log.error('Error getting lines by station', { body: request.body, error: error.message });
3030
if (error.response) {
@@ -47,13 +47,13 @@ export async function getStationByLine(request, reply) {
4747
const { EventDate, OperatorId, OfficelineId, Directions } = request.body;
4848
const date = formatDate(new Date(EventDate));
4949
request.log.info('Getting stations by line', { EventDate: date, OfficelineId, OperatorId, Directions });
50-
const response = await govRequest.post('/trafficLicensing/GetStationToLine', {
50+
const response = await GovRequest.post('/trafficLicensing/GetStationToLine', {
5151
EventDate: date,
5252
OperatorId,
5353
OfficelineId,
5454
Directions: [Directions],
5555
});
56-
return reply.status(200).send({ data: response.data.Data, success: true });
56+
return reply.status(200).send({ data: response.Data, success: true });
5757
} catch (error) {
5858
request.log.error('Error getting stations by line', { body: request.body, error: error.message });
5959
if (error.response) {
@@ -74,8 +74,8 @@ export async function getStationByLine(request, reply) {
7474
export async function getSubjects(request, reply) {
7575
try {
7676
request.log.info('Getting subjects');
77-
const response = await govRequest.post('/ListProvider/GetList', { listName: 'subject_type_vehicles' });
78-
return reply.status(200).send({ data: response.data.Data.List, success: true });
77+
const response = await GovRequest.post('/ListProvider/GetList', { listName: 'subject_type_vehicles' });
78+
return reply.status(200).send({ data: response.Data.List, success: true });
7979
} catch (error) {
8080
request.log.error('Error getting subjects', { body: request.body, error: error.message });
8181
if (error.response) {
@@ -97,8 +97,8 @@ export async function getTrainStations(request, reply) {
9797
try {
9898
const { StationTypeId } = request.body;
9999
request.log.info('Getting train stations', { StationTypeId });
100-
const response = await govRequest.post('/trafficLicensing/GetTrainStations', { StationTypeId });
101-
return reply.status(200).send({ data: response.data.Data, success: true });
100+
const response = await GovRequest.post('/trafficLicensing/GetTrainStations', { StationTypeId });
101+
return reply.status(200).send({ data: response.Data, success: true });
102102
} catch (error) {
103103
request.log.error('Error getting train stations', { body: request.body, error: error.message });
104104
if (error.response) {
@@ -119,8 +119,8 @@ export async function getTrainStations(request, reply) {
119119
export async function getPniya(request, reply) {
120120
try {
121121
request.log.info('Getting pniya');
122-
const response = await govRequest.post('/ListProvider/GetList', { listName: 'pniya' });
123-
return reply.status(200).send({ data: response.data.Data.List, success: true });
122+
const response = await GovRequest.post('/ListProvider/GetList', { listName: 'pniya' });
123+
return reply.status(200).send({ data: response.Data.List, success: true });
124124
} catch (error) {
125125
request.log.error('Error getting pniya', { body: request.body, error: error.message });
126126
if (error.response) {
@@ -141,8 +141,8 @@ export async function getPniya(request, reply) {
141141
export async function getNotRealNumbers(request, reply) {
142142
try {
143143
request.log.info('Getting not real numbers');
144-
const response = await govRequest.post('/ListProvider/GetList', { listName: 'notrealnumbers' });
145-
return reply.status(200).send({ data: response.data.Data.List, success: true });
144+
const response = await GovRequest.post('/ListProvider/GetList', { listName: 'notrealnumbers' });
145+
return reply.status(200).send({ data: response.Data.List, success: true });
146146
} catch (error) {
147147
request.log.error('Error getting not real numbers', { body: request.body, error: error.message });
148148
if (error.response) {
@@ -165,8 +165,8 @@ export async function getLinesByLine(request, reply) {
165165
const { EventDate, OperatorId, OperatorLineId } = request.body;
166166
const date = formatDate(new Date(EventDate));
167167
request.log.info('Getting lines by line ID', { EventDate: date, OperatorId, OperatorLineId });
168-
const response = await govRequest.post('/trafficLicensing/GetLines', { EventDate: date, OperatorId, OperatorLineId });
169-
return reply.status(200).send({ data: response.data.Data, success: true });
168+
const response = await GovRequest.post('/trafficLicensing/GetLines', { EventDate: date, OperatorId, OperatorLineId });
169+
return reply.status(200).send({ data: response.Data, success: true });
170170
} catch (error) {
171171
request.log.error('Error getting lines by line ID', { body: request.body, error: error.message });
172172
if (error.response) {
@@ -187,8 +187,8 @@ export async function getLinesByLine(request, reply) {
187187
export async function getCities(request, reply) {
188188
try {
189189
request.log.info('Getting cities');
190-
const response = await govRequest.post('/trafficLicensing/GetCities');
191-
return reply.status(200).send({ data: response.data.Data, success: true });
190+
const response = await GovRequest.post('/trafficLicensing/GetCities');
191+
return reply.status(200).send({ data: response.Data, success: true });
192192
} catch (error) {
193193
request.log.error('Error getting cities', { error: error.message });
194194

@@ -211,8 +211,8 @@ export async function getCities(request, reply) {
211211
export async function getOperators(request, reply) {
212212
try {
213213
request.log.info('Getting operators');
214-
const response = await govRequest.post('/trafficLicensing/GetOperators');
215-
return reply.status(200).send({ data: response.data.Data, success: true });
214+
const response = await GovRequest.post('/trafficLicensing/GetOperators');
215+
return reply.status(200).send({ data: response.Data, success: true });
216216
} catch (error) {
217217
request.log.error('Error getting operators', { error: error.message });
218218

@@ -236,8 +236,8 @@ export async function getTime(request, reply) {
236236
try {
237237
request.log.info('Getting current time');
238238
const timestamp = Date.now();
239-
const response = await govRequest.get(`/TSA/GetTime?_=${timestamp}`);
240-
return reply.status(200).send({ data: { serverTime: response.data }, success: true });
239+
const response = await GovRequest.get(`/TSA/GetTime?_=${timestamp}`);
240+
return reply.status(200).send({ data: { serverTime: response }, success: true });
241241
} catch (error) {
242242
request.log.error('Error getting current time', { error: error.message });
243243
if (error.response) {

src/schemas/gov.schema.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export const getSubjectsSchema = {
118118
export const getTrainStationsSchema = {
119119
tags: ['Government Transportation'],
120120
summary: 'Get train stations',
121-
description: 'Retrieve train stations by station type\n7 - Israel Train\n4 - Kfir Light Train\n13 - Tevel Ligh Train',
121+
description: 'Retrieve train stations by station type\n7 - Israel Train\n4 - Kfir Light Train\n18 - Tevel Ligh Train',
122122
body: S.object().prop('StationTypeId', stationTypeId()).required(['StationTypeId']),
123123
response: {
124124
200: commonSuccessResponse(S.array().items(S.ref('StationModel'))),

src/schemas/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const commonSuccessResponse = (itemsSchema) =>
1212
export const dataCodeModel = S.object()
1313
.id('DataCodeModel')
1414
.prop('dataText', S.anyOf([S.string(), S.null()]))
15-
.prop('dataCode', S.anyOf([S.string(), S.number(), S.null()]));
15+
.prop('dataCode', S.anyOf([S.number(), S.null()]));
1616

1717
export const toggle = S.string().id('ToggleModel').enum(['1', '2']);
1818

src/utils/govRequest.js

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
11
import ky from 'ky';
22

3-
// Base URL for government services
4-
const GOV_BASE_URL = 'https://esb.gov.il/govServiceList';
5-
63
/**
74
* Helper object to make government API requests
85
*/
9-
export const govRequest = {
10-
globalOptions: { timeout: 30000 },
11-
get(endpoint, options = {}) {
12-
const config = { ...this.globalOptions, ...options };
13-
const url = `${GOV_BASE_URL}${endpoint}`;
14-
return ky.get(url, config);
15-
},
16-
post(endpoint, data, options = {}) {
17-
const config = { ...this.globalOptions, ...options };
18-
const url = `${GOV_BASE_URL}${endpoint}`;
19-
return ky.post(url, { json: data, ...config });
20-
},
21-
};
6+
export class GovRequest {
7+
static #url = 'https://esb.gov.il/govServiceList';
8+
static #timeout = 30000;
9+
static #keyMap = { DataCode: 'dataCode', DataText: 'dataText' };
10+
11+
static #toCamelCase(obj) {
12+
if (typeof obj !== 'object' || obj === null) return obj;
13+
if (Array.isArray(obj)) return obj.map((item) => this.#toCamelCase(item));
14+
const result = {};
15+
for (const key of Object.keys(obj)) {
16+
result[this.#keyMap[key] || key] = this.#toCamelCase(obj[key]);
17+
}
18+
return result;
19+
}
20+
21+
static async get(endpoint, options = {}) {
22+
const url = `${this.#url}${endpoint}`;
23+
const response = await ky.get(url, { timeout: this.#timeout, ...options });
24+
return this.#toCamelCase(await response.json());
25+
}
26+
static async post(endpoint, data, options = {}) {
27+
const url = `${this.#url}${endpoint}`;
28+
const response = await ky.post(url, { timeout: this.#timeout, json: data, ...options });
29+
return this.#toCamelCase(await response.json());
30+
}
31+
}

tests/gov.test.js

Lines changed: 73 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { expect } from 'chai';
22
import sinon from 'sinon';
33

44
import * as govController from '../src/controllers/gov.controller.js';
5-
import { govRequest } from '../src/utils/govRequest.js';
5+
import { GovRequest } from '../src/utils/govRequest.js';
66
import { cleanup, createMockReply, createMockRequest } from './test.utils.js';
77

88
describe('Government API Controller', () => {
@@ -14,28 +14,46 @@ describe('Government API Controller', () => {
1414
beforeEach(() => {
1515
request = createMockRequest();
1616
reply = createMockReply();
17-
post = sinon.stub(govRequest, 'post');
18-
get = sinon.stub(govRequest, 'get');
17+
post = sinon.stub(GovRequest, 'post');
18+
get = sinon.stub(GovRequest, 'get');
1919
});
2020

2121
afterEach(() => {
2222
cleanup();
2323
});
2424

2525
it('getLinesByStation should return expected data', async () => {
26-
const mockLine = {
27-
lineCode: 11005,
28-
lineText: '5',
29-
operatorId: 3,
30-
eventDate: '2025-05-13T00:00:00',
31-
directionCode: 3,
32-
directionText: null,
33-
destinationCity: { DataCode: 2800, DataText: 'קרית שמונה' },
34-
originCity: { DataCode: 2800, DataText: 'קרית שמונה' },
35-
message: null,
26+
const expected = {
27+
data: [
28+
{
29+
lineCode: 11005,
30+
lineText: '5',
31+
operatorId: 3,
32+
eventDate: '2025-05-13T00:00:00',
33+
directionCode: 3,
34+
directionText: null,
35+
destinationCity: { dataCode: 2800, dataText: 'קרית שמונה' },
36+
originCity: { dataCode: 2800, dataText: 'קרית שמונה' },
37+
message: null,
38+
},
39+
],
40+
success: true,
3641
};
37-
const expected = { data: [mockLine], success: true };
38-
post.resolves({ data: { Data: [mockLine] } });
42+
post.resolves({
43+
Data: [
44+
{
45+
lineCode: 11005,
46+
lineText: '5',
47+
operatorId: 3,
48+
eventDate: '2025-05-13T00:00:00',
49+
directionCode: 3,
50+
directionText: null,
51+
destinationCity: { dataCode: 2800, dataText: 'קרית שמונה' },
52+
originCity: { dataCode: 2800, dataText: 'קרית שמונה' },
53+
message: null,
54+
},
55+
],
56+
});
3957
request.body = { EventDate: 1747083600000, OperatorId: 3, StationId: 57865 };
4058
await govController.getLinesByStation(request, reply);
4159
expect(reply.sendCalledWith).to.deep.equal(expected);
@@ -50,7 +68,7 @@ describe('Government API Controller', () => {
5068
stationFullName: 'בית ספר קורצק/יהודה הלוי, קרית שמונה',
5169
};
5270
const expected = { data: [mockStation], success: true };
53-
post.resolves({ data: { Data: [mockStation] } });
71+
post.resolves({ Data: [mockStation] });
5472
request.body = { EventDate: 1747083600000, OperatorId: 3, OfficelineId: 12083, Directions: 1 };
5573
await govController.getStationByLine(request, reply);
5674
expect(reply.sendCalledWith).to.deep.equal(expected);
@@ -66,7 +84,7 @@ describe('Government API Controller', () => {
6684
subject_code: '3',
6785
};
6886
const expected = { data: [mockSubject], success: true };
69-
post.resolves({ data: { Data: { List: [mockSubject] } } });
87+
post.resolves({ Data: { List: [mockSubject] } });
7088
await govController.getSubjects(request, reply);
7189
expect(reply.sendCalledWith).to.deep.equal(expected);
7290
});
@@ -80,7 +98,7 @@ describe('Government API Controller', () => {
8098
stationFullName: null,
8199
};
82100
const expected = { data: [mockTrainStation], success: true };
83-
post.resolves({ data: { Data: [mockTrainStation] } });
101+
post.resolves({ Data: [mockTrainStation] });
84102
request.body = { StationTypeId: 7 };
85103
await govController.getTrainStations(request, reply);
86104
expect(reply.sendCalledWith).to.deep.equal(expected);
@@ -89,33 +107,51 @@ describe('Government API Controller', () => {
89107
it('getPniya should return expected data', async () => {
90108
const mockPniya = { RowNumber: '1', code: '0', pniya: 'אוטובוס' };
91109
const expected = { data: [mockPniya], success: true };
92-
post.resolves({ data: { Data: { List: [mockPniya] } } });
110+
post.resolves({ Data: { List: [mockPniya] } });
93111
await govController.getPniya(request, reply);
94112
expect(reply.sendCalledWith).to.deep.equal(expected);
95113
});
96114

97115
it('getNotRealNumbers should return expected data', async () => {
98116
const mockNotReal = { RowNumber: '1', Code: '1', IdNum: '123456782' };
99117
const expected = { data: [mockNotReal], success: true };
100-
post.resolves({ data: { Data: { List: [mockNotReal] } } });
118+
post.resolves({ Data: { List: [mockNotReal] } });
101119
await govController.getNotRealNumbers(request, reply);
102120
expect(reply.sendCalledWith).to.deep.equal(expected);
103121
});
104122

105123
it('getLinesByLine should return expected data', async () => {
106-
const mockLine = {
107-
lineCode: 10083,
108-
lineText: '83',
109-
operatorId: 3,
110-
eventDate: '2025-05-13T00:00:00',
111-
directionCode: 2,
112-
directionText: 'חיפה-חיפה',
113-
destinationCity: { DataCode: 4000, DataText: 'חיפה' },
114-
originCity: { DataCode: 4000, DataText: 'חיפה' },
115-
message: null,
124+
const expected = {
125+
data: [
126+
{
127+
lineCode: 10083,
128+
lineText: '83',
129+
operatorId: 3,
130+
eventDate: '2025-05-13T00:00:00',
131+
directionCode: 2,
132+
directionText: 'חיפה-חיפה',
133+
destinationCity: { dataCode: 4000, dataText: 'חיפה' },
134+
originCity: { dataCode: 4000, dataText: 'חיפה' },
135+
message: null,
136+
},
137+
],
138+
success: true,
116139
};
117-
const expected = { data: [mockLine], success: true };
118-
post.resolves({ data: { Data: [mockLine] } });
140+
post.resolves({
141+
Data: [
142+
{
143+
lineCode: 10083,
144+
lineText: '83',
145+
operatorId: 3,
146+
eventDate: '2025-05-13T00:00:00',
147+
directionCode: 2,
148+
directionText: 'חיפה-חיפה',
149+
destinationCity: { dataCode: 4000, dataText: 'חיפה' },
150+
originCity: { dataCode: 4000, dataText: 'חיפה' },
151+
message: null,
152+
},
153+
],
154+
});
119155
request.body = {
120156
EventDate: 1747083600000,
121157
OperatorId: 3,
@@ -126,25 +162,23 @@ describe('Government API Controller', () => {
126162
});
127163

128164
it('getCities should return expected data', async () => {
129-
const mockCity = { DataCode: 5000, DataText: 'תל אביב יפו' };
130-
const expected = { data: [mockCity], success: true };
131-
post.resolves({ data: { Data: [mockCity] } });
165+
const expected = { data: [{ dataCode: 5000, dataText: 'תל אביב יפו' }], success: true };
166+
post.resolves({ Data: [{ dataCode: 5000, dataText: 'תל אביב יפו' }] });
132167
await govController.getCities(request, reply);
133168
expect(reply.sendCalledWith).to.deep.equal(expected);
134169
});
135170

136171
it('getOperators should return expected data', async () => {
137-
const mockOperator = { DataCode: 3, DataText: 'אגד' };
138-
const expected = { data: [mockOperator], success: true };
139-
post.resolves({ data: { Data: [mockOperator] } });
172+
const expected = { data: [{ dataCode: 3, dataText: 'אגד' }], success: true };
173+
post.resolves({ Data: [{ dataCode: 3, dataText: 'אגד' }] });
140174
await govController.getOperators(request, reply);
141175
expect(reply.sendCalledWith).to.deep.equal(expected);
142176
});
143177

144178
it('getTime should return expected data', async () => {
145179
const mockTime = '7/8/2025 10:09:25 PM';
146180
const expected = { data: { serverTime: mockTime }, success: true };
147-
get.resolves({ data: mockTime });
181+
get.resolves(mockTime);
148182
await govController.getTime(request, reply);
149183
expect(reply.sendCalledWith).to.deep.equal(expected);
150184
});

0 commit comments

Comments
 (0)