Skip to content

Commit

Permalink
feat: update proxy tests for cm360 (#3039)
Browse files Browse the repository at this point in the history
  • Loading branch information
utsabc authored Feb 7, 2024
2 parents 30c4eca + 99f5cb2 commit 0504ffa
Show file tree
Hide file tree
Showing 17 changed files with 2,257 additions and 811 deletions.
11 changes: 10 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@
"ua-parser-js": "^1.0.37",
"unset-value": "^2.0.1",
"uuid": "^9.0.0",
"valid-url": "^1.0.9"
"valid-url": "^1.0.9",
"zod": "^3.22.4"
},
"devDependencies": {
"@commitlint/config-conventional": "^17.6.3",
Expand Down
7 changes: 6 additions & 1 deletion src/controllers/delivery.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable prefer-destructuring */
/* eslint-disable sonarjs/no-duplicate-string */
import { Context } from 'koa';
import { isDefinedAndNotNullAndNotEmpty } from '@rudderstack/integrations-lib';
import { MiscService } from '../services/misc';
import {
DeliveryV1Response,
Expand Down Expand Up @@ -84,7 +85,11 @@ export class DeliveryController {
);
}
ctx.body = { output: deliveryResponse };
ControllerUtility.deliveryPostProcess(ctx, deliveryResponse.status);
if (isDefinedAndNotNullAndNotEmpty(deliveryResponse.authErrorCategory)) {
ControllerUtility.deliveryPostProcess(ctx, deliveryResponse.status);
} else {
ControllerUtility.deliveryPostProcess(ctx);
}

logger.debug('Native(Delivery):: Response from transformer::', JSON.stringify(ctx.body));
return ctx;
Expand Down
4 changes: 3 additions & 1 deletion src/services/destination/postTransformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,11 @@ export class DestinationPostTransformationService {
const resp = {
response: responses,
statTags: errObj.statTags,
authErrorCategory: errObj.authErrorCategory,
message: errObj.message.toString(),
status: errObj.status,
...(errObj.authErrorCategory && {
authErrorCategory: errObj.authErrorCategory,
}),
} as DeliveryV1Response;

ErrorReportingService.reportError(error, metaTo.errorContext, resp);
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type ProxyV0Request = {
};
files?: Record<string, unknown>;
metadata: ProxyMetdata;
destinationConfig: Record<string, unknown>;
};

type ProxyV1Request = {
Expand Down
139 changes: 139 additions & 0 deletions src/types/zodTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { z } from 'zod';
import { isDefinedAndNotNullAndNotEmpty } from '@rudderstack/integrations-lib';
import { isHttpStatusSuccess } from '../v0/util';

export const ProxyMetadataSchema = z.object({
jobId: z.number(),
attemptNum: z.number(),
userId: z.string(),
sourceId: z.string(),
destinationId: z.string(),
workspaceId: z.string(),
secret: z.record(z.unknown()),
destInfo: z.object({}).optional(),
omitempty: z.record(z.unknown()).optional(),
dontBatch: z.boolean(),
});

export const ProxyV0RequestSchema = z.object({
version: z.string(),
type: z.string(),
method: z.string(),
endpoint: z.string(),
userId: z.string(),
headers: z.record(z.unknown()).optional(),
params: z.record(z.unknown()).optional(),
body: z
.object({
JSON: z.record(z.unknown()).optional(),
JSON_ARRAY: z.record(z.unknown()).optional(),
XML: z.record(z.unknown()).optional(),
FORM: z.record(z.unknown()).optional(),
})
.optional(),
files: z.record(z.unknown()).optional(),
metadata: ProxyMetadataSchema,
destinationConfig: z.record(z.unknown()),
});

export const ProxyV1RequestSchema = z.object({
version: z.string(),
type: z.string(),
method: z.string(),
endpoint: z.string(),
userId: z.string(),
headers: z.record(z.unknown()).optional(),
params: z.record(z.unknown()).optional(),
body: z
.object({
JSON: z.record(z.unknown()).optional(),
JSON_ARRAY: z.record(z.unknown()).optional(),
XML: z.record(z.unknown()).optional(),
FORM: z.record(z.unknown()).optional(),
})
.optional(),
files: z.record(z.unknown()).optional(),
metadata: z.array(ProxyMetadataSchema),
destinationConfig: z.record(z.unknown()),
});

const validateStatTags = (data: any) => {
if (!isHttpStatusSuccess(data.status)) {
return isDefinedAndNotNullAndNotEmpty(data.statTags);
}
return true;
};

const validateAuthErrorCategory = (data: any) => {
if (!isHttpStatusSuccess(data.status)) {
return isDefinedAndNotNullAndNotEmpty(data.authErrorCategory);
}
return true;
};

export const DeliveryV0ResponseSchema = z
.object({
status: z.number(),
message: z.string(),
destinationResponse: z.unknown(),
statTags: z.record(z.unknown()).optional(),
authErrorCategory: z.string().optional(),
})
.refine(validateStatTags, {
// eslint-disable-next-line sonarjs/no-duplicate-string
message: "statTags can't be empty when status is not a 2XX",
path: ['statTags'], // Pointing out which field is invalid
});

export const DeliveryV0ResponseSchemaForOauth = z
.object({
status: z.number(),
message: z.string(),
destinationResponse: z.unknown(),
statTags: z.record(z.unknown()).optional(),
authErrorCategory: z.string().optional(),
})
.refine(validateStatTags, {
message: "statTags can't be empty when status is not a 2XX",
path: ['statTags'], // Pointing out which field is invalid
})
.refine(validateAuthErrorCategory, {
message: "authErrorCategory can't be empty when status is not a 2XX",
path: ['authErrorCategory'], // Pointing out which field is invalid
});

const DeliveryJobStateSchema = z.object({
error: z.string(),
statusCode: z.number(),
metadata: ProxyMetadataSchema,
});

export const DeliveryV1ResponseSchema = z
.object({
status: z.number(),
message: z.string(),
statTags: z.record(z.unknown()).optional(),
authErrorCategory: z.string().optional(),
response: z.array(DeliveryJobStateSchema),
})
.refine(validateStatTags, {
message: "statTags can't be empty when status is not a 2XX",
path: ['statTags'], // Pointing out which field is invalid
});

export const DeliveryV1ResponseSchemaForOauth = z
.object({
status: z.number(),
message: z.string(),
statTags: z.record(z.unknown()).optional(),
authErrorCategory: z.string().optional(),
response: z.array(DeliveryJobStateSchema),
})
.refine(validateStatTags, {
message: "statTags can't be empty when status is not a 2XX",
path: ['statTags'], // Pointing out which field is invalid
})
.refine(validateAuthErrorCategory, {
message: "authErrorCategory can't be empty when status is not a 2XX",
path: ['authErrorCategory'], // Pointing out which field is invalid
});
109 changes: 109 additions & 0 deletions test/integrations/common/google/network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Ads API
// Ref: https://developers.google.com/google-ads/api/docs/get-started/common-errors

export const networkCallsData = [
{
description: 'Mock response depicting CREDENTIALS_MISSING error',
httpReq: {
method: 'post',
url: 'https://googleapis.com/test_url_for_credentials_missing',
},
httpRes: {
data: {
error: {
code: 401,
message:
'Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.',
errors: [
{
message: 'Login Required.',
domain: 'global',
reason: 'required',
location: 'Authorization',
locationType: 'header',
},
],
status: 'UNAUTHENTICATED',
details: [
{
'@type': 'type.googleapis.com/google.rpc.ErrorInfo',
reason: 'CREDENTIALS_MISSING',
domain: 'googleapis.com',
metadata: {
method: 'google.ads.xfa.op.v4.DfareportingConversions.Batchinsert',
service: 'googleapis.com',
},
},
],
},
},
status: 401,
},
},
{
description: 'Mock response depicting ACCESS_TOKEN_SCOPE_INSUFFICIENT error',
httpReq: {
method: 'post',
url: 'https://googleapis.com/test_url_for_access_token_scope_insufficient',
},
httpRes: {
data: {
error: {
code: 403,
message: 'Request had insufficient authentication scopes.',
errors: [
{
message: 'Insufficient Permission',
domain: 'global',
reason: 'insufficientPermissions',
},
],
status: 'PERMISSION_DENIED',
details: [
{
'@type': 'type.googleapis.com/google.rpc.ErrorInfo',
reason: 'ACCESS_TOKEN_SCOPE_INSUFFICIENT',
domain: 'googleapis.com',
metadata: {
service: 'gmail.googleapis.com',
method: 'caribou.api.proto.MailboxService.GetProfile',
},
},
],
},
},
status: 403,
},
},
{
description: 'Mock response for google.auth.exceptions.RefreshError invalid_grant error',
httpReq: {
method: 'post',
url: 'https://googleapis.com/test_url_for_invalid_grant',
},
httpRes: {
data: {
error: {
code: 403,
message: 'invalid_grant',
error_description: 'Bad accesss',
},
},
status: 403,
},
},
{
description: 'Mock response for google.auth.exceptions.RefreshError refresh_token error',
httpReq: {
method: 'post',
url: 'https://googleapis.com/test_url_for_refresh_error',
},
httpRes: {
data: {
error: 'unauthorized',
error_description: 'Access token expired: 2020-10-20T12:00:00.000Z',
},
status: 401,
},
},
];
62 changes: 62 additions & 0 deletions test/integrations/common/network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
export const networkCallsData = [
{
description: 'Mock response depicting SERVICE NOT AVAILABLE error',
httpReq: {
method: 'post',
url: 'https://random_test_url/test_for_service_not_available',
},
httpRes: {
data: {
error: {
message: 'Service Unavailable',
description:
'The server is currently unable to handle the request due to temporary overloading or maintenance of the server. Please try again later.',
},
},
status: 503,
},
},
{
description: 'Mock response depicting INTERNAL SERVER ERROR error',
httpReq: {
method: 'post',
url: 'https://random_test_url/test_for_internal_server_error',
},
httpRes: {
data: 'Internal Server Error',
status: 500,
},
},
{
description: 'Mock response depicting GATEWAY TIME OUT error',
httpReq: {
method: 'post',
url: 'https://random_test_url/test_for_gateway_time_out',
},
httpRes: {
data: 'Gateway Timeout',
status: 504,
},
},
{
description: 'Mock response depicting null response',
httpReq: {
method: 'post',
url: 'https://random_test_url/test_for_null_response',
},
httpRes: {
data: null,
status: 500,
},
},
{
description: 'Mock response depicting null and no status',
httpReq: {
method: 'post',
url: 'https://random_test_url/test_for_null_and_no_status',
},
httpRes: {
data: null,
},
},
];
Loading

0 comments on commit 0504ffa

Please sign in to comment.