Skip to content

Commit fed5e9b

Browse files
Validation refactoring per code review
1 parent c18637e commit fed5e9b

File tree

7 files changed

+129
-161
lines changed

7 files changed

+129
-161
lines changed

packages/optimizely-sdk/lib/plugins/odp/graphql_manager.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,29 +74,23 @@ export class GraphqlManager implements IGraphQLManager {
7474
* @param segmentsToCheck Audience segments to check for experiment inclusion
7575
*/
7676
public async fetchSegments(apiKey: string, apiEndpoint: string, userKey: string, userValue: string, segmentsToCheck: string[]): Promise<string[] | null> {
77-
if (!apiEndpoint || !apiKey) {
78-
this._logger.log(LogLevel.ERROR, 'No ApiEndpoint or ApiKey set before attempting to fetch segments');
79-
return null;
77+
if (segmentsToCheck?.length === 0) {
78+
return EMPTY_SEGMENTS_COLLECTION;
8079
}
8180

82-
const parameters = new QuerySegmentsParameters({
81+
const parameters = new QuerySegmentsParameters(
8382
apiKey,
8483
apiEndpoint,
8584
userKey,
8685
userValue,
8786
segmentsToCheck,
88-
});
89-
87+
);
9088
const segmentsResponse = await this._odpClient.querySegments(parameters);
9189
if (!segmentsResponse) {
9290
this._logger.log(LogLevel.ERROR, 'Audience segments fetch failed (network error)');
9391
return null;
9492
}
9593

96-
if (segmentsToCheck?.length === 0) {
97-
return EMPTY_SEGMENTS_COLLECTION;
98-
}
99-
10094
const parsedSegments = this.parseSegmentsResponseJson(segmentsResponse);
10195
if (!parsedSegments) {
10296
this._logger.log(LogLevel.ERROR, 'Audience segments fetch failed (decode error)');
@@ -131,6 +125,8 @@ export class GraphqlManager implements IGraphQLManager {
131125

132126
try {
133127
jsonObject = JSON.parse(jsonResponse);
128+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
129+
// @ts-ignore
134130
// eslint-disable-next-line @typescript-eslint/no-explicit-any
135131
} catch (error: any) {
136132
this._errorHandler.handleError(error);

packages/optimizely-sdk/lib/plugins/odp/odp_client.ts

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,7 @@ export class OdpClient implements IOdpClient {
6767
* @returns JSON response string from ODP or null
6868
*/
6969
public async querySegments(parameters: QuerySegmentsParameters): Promise<string | null> {
70-
const { apiEndpoint, apiKey, httpVerb, userKey, userValue, segmentsToCheck } = parameters;
71-
72-
if (!apiEndpoint || !apiKey) {
73-
this._logger.log(LogLevel.ERROR, 'No ApiHost or ApiKey set before querying segments');
74-
return null;
75-
}
76-
77-
if (!userKey || !userValue) {
78-
this._logger.log(LogLevel.ERROR, 'No UserKey or UserValue set before querying segments');
79-
return null;
80-
}
81-
82-
if (segmentsToCheck?.length === 0) {
83-
return '';
84-
}
70+
const { apiEndpoint, apiKey, httpVerb } = parameters;
8571

8672
const method = httpVerb;
8773
const url = apiEndpoint;
@@ -95,6 +81,8 @@ export class OdpClient implements IOdpClient {
9581
try {
9682
const request = this._requestHandler.makeRequest(url, headers, method, data);
9783
response = await request.responsePromise;
84+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
85+
// @ts-ignore
9886
// eslint-disable-next-line @typescript-eslint/no-explicit-any
9987
} catch (error: any) {
10088
this._errorHandler.handleError(error);
@@ -118,17 +106,12 @@ export class OdpClient implements IOdpClient {
118106
public async sendEvents(parameters: SendEventsParameters): Promise<number | null> {
119107
const { apiEndpoint, apiKey, httpVerb, events } = parameters;
120108

121-
if (!apiEndpoint || !apiKey) {
122-
this._logger.log(LogLevel.ERROR, 'No ApiEndpoint or ApiKey set before attempting to send ODP events');
123-
return null;
124-
}
125-
126109
if (events?.length === 0) {
127110
return null;
128111
}
129112

130113
const method = httpVerb;
131-
const url = apiEndpoint ?? '';
114+
const url = apiEndpoint;
132115
const headers = {
133116
'Content-Type': 'application/json',
134117
'x-api-key': apiKey,
@@ -139,6 +122,8 @@ export class OdpClient implements IOdpClient {
139122
try {
140123
const request = this._requestHandler.makeRequest(url, headers, method, data);
141124
response = await request.responsePromise;
125+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
126+
// @ts-ignore
142127
// eslint-disable-next-line @typescript-eslint/no-explicit-any
143128
} catch (error: any) {
144129
this._errorHandler.handleError(error);

packages/optimizely-sdk/lib/plugins/odp/odp_request_parameters.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,34 @@
1515
*/
1616

1717
export interface IOdpRequestParameters {
18-
apiKey?: string;
19-
apiEndpoint?: string;
18+
apiKey: string;
19+
apiEndpoint: string;
2020
httpVerb: string;
2121
}
2222

2323
export abstract class OdpRequestParameters implements IOdpRequestParameters {
2424
/**
2525
* Optimizely Data Platform API key
2626
*/
27-
public apiKey?: string;
27+
public readonly apiKey: string;
2828

2929
/**
3030
* Fully-qualified URL to ODP events endpoint
3131
*/
32-
public apiEndpoint?: string;
32+
public readonly apiEndpoint: string;
3333

3434
/**
3535
* HTTP Verb used to send request
3636
*/
37-
public abstract readonly httpVerb: string;
37+
public readonly httpVerb: string;
38+
39+
protected constructor(apiKey: string, apiEndpoint: string, httpVerb: string) {
40+
if (!apiEndpoint || !apiKey) {
41+
throw new Error('Parameters apiKey and apiEndpoint are required');
42+
}
43+
44+
this.apiKey = apiKey;
45+
this.apiEndpoint = apiEndpoint;
46+
this.httpVerb = httpVerb ?? 'POST';
47+
}
3848
}

packages/optimizely-sdk/lib/plugins/odp/query_segments_parameters.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,32 @@ export class QuerySegmentsParameters extends OdpRequestParameters {
2323
/**
2424
* 'vuid' or 'fs_user_id' (client device id or fullstack id)
2525
*/
26-
public userKey?: string;
26+
public readonly userKey: string;
2727

2828
/**
2929
* Value for the user key
3030
*/
31-
public userValue?: string;
31+
public readonly userValue: string;
3232

3333
/**
3434
* Audience segments to check for inclusion in the experiment
3535
*/
36-
public segmentsToCheck?: string[];
36+
public readonly segmentsToCheck: string[];
3737

38-
/**
39-
* HTTP Verb used to send request
40-
*/
41-
public readonly httpVerb = 'POST';
38+
constructor(apiKey: string, apiEndpoint: string, userKey: string, userValue: string, segmentsToCheck: string[]) {
39+
super(apiKey, apiEndpoint, 'POST');
40+
41+
if (!userKey || !userValue) {
42+
throw new Error('Parameters userKey or userValue are required');
43+
}
44+
45+
if (segmentsToCheck.length < 1) {
46+
throw new Error('Parameter segmentsToCheck must have elements');
47+
}
4248

43-
constructor(parameters: { apiKey: string, apiEndpoint: string, userKey: string, userValue: string, segmentsToCheck: string[] }) {
44-
super();
45-
Object.assign(this, parameters);
49+
this.userKey = userKey;
50+
this.userValue = userValue;
51+
this.segmentsToCheck = segmentsToCheck;
4652
}
4753

4854
/**

packages/optimizely-sdk/lib/plugins/odp/send_events_parameters.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@ export class SendEventsParameters extends OdpRequestParameters {
2424
/**
2525
* Collection of ODP events to transmit
2626
*/
27-
public events?: OdpEvent[];
27+
public readonly events: OdpEvent[];
2828

29-
/**
30-
* HTTP Verb used to send request
31-
*/
32-
public httpVerb = 'POST';
29+
constructor(apiKey: string, apiEndpoint: string, events: OdpEvent[]) {
30+
super(apiKey, apiEndpoint, 'POST');
31+
32+
if (OdpEvent.length < 1) {
33+
throw new Error('Parameter events must have elements');
34+
}
3335

34-
constructor(parameters: { apiKey: string, apiEndpoint: string, events: OdpEvent[] }) {
35-
super();
36-
Object.assign(this, parameters);
36+
this.events = events;
3737
}
3838

3939
/**

packages/optimizely-sdk/tests/odpQuerySegments.spec.ts

Lines changed: 57 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@ import { QuerySegmentsParameters } from '../lib/plugins/odp/query_segments_param
2323
import { BrowserRequestHandler } from '../lib/utils/http_request_handler/browser_request_handler';
2424
import { NodeRequestHandler } from '../lib/utils/http_request_handler/node_request_handler';
2525

26-
const MOCK_QUERY_PARAMETERS = new QuerySegmentsParameters({
27-
apiKey: 'not-real-api-key',
28-
apiEndpoint: 'https://api.example.com/v3/graphql',
29-
userKey: 'fs_user_id',
30-
userValue: 'mock-user-id',
31-
segmentsToCheck: [
26+
const MOCK_QUERY_PARAMETERS = new QuerySegmentsParameters(
27+
'not-real-api-key',
28+
'https://api.example.com/v3/graphql',
29+
'fs_user_id',
30+
'mock-user-id',
31+
[
3232
'has_email',
3333
'has_email_opted_in',
3434
'push_on_sale',
3535
],
36-
});
36+
);
3737
const VALID_RESPONSE_JSON = {
3838
'data': {
3939
'customer': {
@@ -78,85 +78,64 @@ describe('OdpClient Query Segments', () => {
7878
resetCalls(mockNodeRequestHandler);
7979
});
8080

81-
it('should handle missing API Endpoint', async () => {
82-
const missingApiEndpoint = new QuerySegmentsParameters({
83-
apiKey: 'apiKey',
84-
apiEndpoint: '',
85-
userKey: 'userKey',
86-
userValue: 'userValue',
87-
segmentsToCheck: ['segmentToCheck'],
88-
});
89-
const client = new OdpClient(instance(mockErrorHandler), instance(mockLogger), instance(mockNodeRequestHandler));
90-
91-
await client.querySegments(missingApiEndpoint);
92-
93-
verify(mockErrorHandler.handleError(anything())).never();
94-
verify(mockLogger.log(LogLevel.ERROR, 'No ApiHost or ApiKey set before querying segments')).once();
81+
it('should handle missing API Endpoint', () => {
82+
expect(() => {
83+
new QuerySegmentsParameters(
84+
'apiKey',
85+
'',
86+
'userKey',
87+
'userValue',
88+
['segmentToCheck'],
89+
);
90+
}).toThrow('Parameters apiKey and apiEndpoint are required');
9591
});
9692

97-
it('should handle missing API Key', async () => {
98-
const missingApiHost = new QuerySegmentsParameters({
99-
apiKey: '',
100-
apiEndpoint: 'apiEndpoint',
101-
userKey: 'userKey',
102-
userValue: 'userValue',
103-
segmentsToCheck: ['segmentToCheck'],
104-
});
105-
const client = new OdpClient(instance(mockErrorHandler), instance(mockLogger), instance(mockNodeRequestHandler));
106-
107-
await client.querySegments(missingApiHost);
108-
109-
verify(mockErrorHandler.handleError(anything())).never();
110-
verify(mockLogger.log(LogLevel.ERROR, 'No ApiHost or ApiKey set before querying segments')).once();
93+
it('should handle missing API Key', () => {
94+
expect(() => {
95+
new QuerySegmentsParameters(
96+
'',
97+
'apiEndpoint',
98+
'userKey',
99+
'userValue',
100+
['segmentToCheck'],
101+
);
102+
}).toThrow('Parameters apiKey and apiEndpoint are required');
111103
});
112104

113-
it('should handle missing User Key', async () => {
114-
const missingApiEndpoint = new QuerySegmentsParameters({
115-
apiKey: 'apiKey',
116-
apiEndpoint: 'apiEndpoint',
117-
userKey: '',
118-
userValue: 'userValue',
119-
segmentsToCheck: ['segmentToCheck'],
120-
});
121-
const client = new OdpClient(instance(mockErrorHandler), instance(mockLogger), instance(mockNodeRequestHandler));
122-
123-
await client.querySegments(missingApiEndpoint);
124-
125-
verify(mockErrorHandler.handleError(anything())).never();
126-
verify(mockLogger.log(LogLevel.ERROR, 'No UserKey or UserValue set before querying segments')).once();
105+
it('should handle missing User Key', () => {
106+
expect(() => {
107+
new QuerySegmentsParameters(
108+
'apiKey',
109+
'apiEndpoint',
110+
'',
111+
'userValue',
112+
['segmentToCheck'],
113+
);
114+
}).toThrow('Parameters userKey or userValue are required');
127115
});
128116

129-
it('should handle missing User Value', async () => {
130-
const missingApiHost = new QuerySegmentsParameters({
131-
apiKey: 'apiKey',
132-
apiEndpoint: 'apiEndpoint',
133-
userKey: 'userKey',
134-
userValue: '',
135-
segmentsToCheck: ['segmentToCheck'],
136-
});
137-
const client = new OdpClient(instance(mockErrorHandler), instance(mockLogger), instance(mockNodeRequestHandler));
138-
139-
await client.querySegments(missingApiHost);
140-
141-
verify(mockErrorHandler.handleError(anything())).never();
142-
verify(mockLogger.log(LogLevel.ERROR, 'No UserKey or UserValue set before querying segments')).once();
117+
it('should handle missing User Value', () => {
118+
expect(() => {
119+
new QuerySegmentsParameters(
120+
'apiKey',
121+
'apiEndpoint',
122+
'userKey',
123+
'',
124+
['segmentToCheck'],
125+
);
126+
}).toThrow('Parameters userKey or userValue are required');
143127
});
144128

145-
it('should handle no segments being requested', async () => {
146-
const missingApiHost = new QuerySegmentsParameters({
147-
apiKey: 'apiKey',
148-
apiEndpoint: 'apiEndpoint',
149-
userKey: 'userKey',
150-
userValue: 'userValue',
151-
segmentsToCheck: [],
152-
});
153-
const client = new OdpClient(instance(mockErrorHandler), instance(mockLogger), instance(mockNodeRequestHandler));
154-
155-
const json = await client.querySegments(missingApiHost);
156-
157-
verify(mockErrorHandler.handleError(anything())).never();
158-
verify(mockLogger.log(anything(), anyString())).never();
159-
expect(json).toBe('');
129+
it('should handle no segments being requested', () => {
130+
expect(() => {
131+
new QuerySegmentsParameters(
132+
'apiKey',
133+
'apiEndpoint',
134+
'userKey',
135+
'userValue',
136+
[],
137+
);
138+
}).toThrow('Parameter segmentsToCheck must have elements');
160139
});
161140

162141
it('Browser: should get mocked segments successfully', async () => {

0 commit comments

Comments
 (0)