From ae621c4308d51fdfadf15e561300b106627d8b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=E1=BB=B3nh=20Tr=E1=BA=A7n=20=C4=90=C4=83ng=20Khoa?= Date: Mon, 7 Oct 2024 14:03:29 +0700 Subject: [PATCH] Develop (#92) * chore: enhance typing * chore: update workflows --- .github/workflows/publish.yml | 2 +- README.md | 23 -------------- src/LoggingInterceptor.ts | 3 +- src/index.ts | 7 +++- src/types.ts | 10 ------ tests/google-ads.spec.ts | 58 ++++++++++++++++----------------- tests/protos.spec.ts | 34 ++++++++++---------- tests/service.spec.ts | 10 +++--- tests/test-utils.ts | 60 +++++++++++------------------------ 9 files changed, 75 insertions(+), 132 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f4f8855..f000ffc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -52,7 +52,7 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') cleanup: diff --git a/README.md b/README.md index 27b86b8..65472ae 100644 --- a/README.md +++ b/README.md @@ -296,29 +296,6 @@ const service = new GoogleAds( See more at [Node.js gRPC Library](https://grpc.github.io/grpc/node/module-src_client_interceptors.html) and some examples [here](https://github.com/grpc/proposal/blob/master/L5-node-client-interceptors.md). -## Bonus - -### Type helper - -If you are using the generated types from the protos, you may run into an issue where the types are referring to values instead of types or missing the following properties from types. You can use the following helper to fix this issue. - -```ts -import { ads, MessageType, MessageFnsKeys } from '@htdangkhoa/google-ads'; - -const { - services: { GoogleAdsRow }, -} = ads.googleads.v17; - -const row: MessageType = { - // ... properties -}; - -// or -const row: Omit = { - // ... properties -}; -``` - ## Development ### Prerequisites diff --git a/src/LoggingInterceptor.ts b/src/LoggingInterceptor.ts index 340ae44..4652b83 100644 --- a/src/LoggingInterceptor.ts +++ b/src/LoggingInterceptor.ts @@ -53,7 +53,6 @@ interface Event { } export class LoggingInterceptor { - private requestLogging: boolean | LoggingOptions; private summaryLogger: Logger; private detailLogger: Logger; @@ -67,7 +66,7 @@ export class LoggingInterceptor { private responseHeaders?: Metadata; private responseStatus?: StatusObject; - constructor(requestLogging: boolean | LoggingOptions) { + constructor(private requestLogging: boolean | LoggingOptions) { this.requestLogging = requestLogging; log4js.configure({ diff --git a/src/index.ts b/src/index.ts index f0b51e2..dc8f7ae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,9 @@ -export * from './generated/index.google.js'; +export * as enums from './generated/index.google.ads.googleads.v17.enums.js'; +export * as common from './generated/index.google.ads.googleads.v17.common.js'; +export * as errors from './generated/index.google.ads.googleads.v17.errors.js'; +export * as resources from './generated/index.google.ads.googleads.v17.resources.js'; +export * as services from './generated/index.google.ads.googleads.v17.services.js'; +export * as rpc from './generated/index.google.rpc.js'; export * from './constants.js'; export * from './Customer.js'; export * from './GoogleAds.js'; diff --git a/src/types.ts b/src/types.ts index 17b6eda..328663d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -89,13 +89,3 @@ export interface OrderBy { export interface Interceptor { interceptCall: GRPCInterceptor; } - -export type MessageFnsKeys = - | 'create' - | 'encode' - | 'decode' - | 'fromJSON' - | 'toJSON' - | 'fromPartial'; - -export type MessageType = Omit; diff --git a/tests/google-ads.spec.ts b/tests/google-ads.spec.ts index c429509..b122b32 100644 --- a/tests/google-ads.spec.ts +++ b/tests/google-ads.spec.ts @@ -7,7 +7,8 @@ import { VERSION, decodePartialFailureError, getGoogleAdsError, - ads, + services, + errors, rpc, } from '../src'; import { @@ -23,18 +24,6 @@ import { MOCK_OAUTH2_CLIENT, } from './test-utils'; -const { - services: { GoogleAdsRow }, - errors: { - GoogleAdsFailure, - ErrorCode, - AuthenticationErrorEnum_AuthenticationError, - RequestErrorEnum_RequestError, - }, -} = ads.googleads.v17; - -const { Status } = rpc; - let service: MockGoogleAds; beforeAll(async () => { @@ -66,9 +55,13 @@ describe('search', () => { it('should be able to search customer_client', async () => { const metadata = new Metadata(); - const { results } = await service + const { results } = (await service .setCustomerId(MOCK_MANAGER_ID) - .mockSearch({ query }, { results: MOCK_CUSTOMER_CLIENTS }, metadata); + .mockSearch( + { query }, + { results: MOCK_CUSTOMER_CLIENTS }, + metadata, + )) as services.SearchGoogleAdsResponse; expect(results).toEqual(MOCK_CUSTOMER_CLIENTS); }); @@ -76,12 +69,13 @@ describe('search', () => { it('should throw ServiceError', async () => { const internalRepr = new Map(); internalRepr.set(FAILURE_KEY, [ - GoogleAdsFailure.fromPartial({ + errors.GoogleAdsFailure.fromPartial({ errors: [ { - error_code: ErrorCode.fromPartial({ + error_code: errors.ErrorCode.fromPartial({ authentication_error: - AuthenticationErrorEnum_AuthenticationError.CUSTOMER_NOT_FOUND, + errors.AuthenticationErrorEnum_AuthenticationError + .CUSTOMER_NOT_FOUND, }), message: 'No customer found for the provided customer id.', }, @@ -89,7 +83,7 @@ describe('search', () => { }), ]); internalRepr.set('grpc-status-details-bin', [ - GoogleAdsFailure.fromPartial({ + errors.GoogleAdsFailure.fromPartial({ errors: [ { message: @@ -154,7 +148,7 @@ describe('searchStream', () => { .setLinkedCustomerId(MOCK_LINKED_CUSTOMER_ID) .mockSearchStream({ query }, { results: MOCK_CAMPAIGNS }); - const campaigns: (typeof GoogleAdsRow)[] = []; + const campaigns: services.GoogleAdsRow[] = []; while (true) { const { value, done } = await stream.next(); @@ -185,11 +179,12 @@ describe('mutate', () => { describe('partial failure', () => { it('should decode partial failure errors if present on the response', async () => { - const failureMessage = GoogleAdsFailure.fromPartial({ + const failureMessage = errors.GoogleAdsFailure.fromPartial({ errors: [ { - error_code: ErrorCode.fromPartial({ - request_error: RequestErrorEnum_RequestError.BAD_RESOURCE_ID, + error_code: errors.ErrorCode.fromPartial({ + request_error: + errors.RequestErrorEnum_RequestError.BAD_RESOURCE_ID, }), message: 'error message', location: { @@ -204,7 +199,8 @@ describe('mutate', () => { ], }); - const failureBuffer = GoogleAdsFailure.encode(failureMessage).finish(); + const failureBuffer = + errors.GoogleAdsFailure.encode(failureMessage).finish(); const response = await service .setCustomerId(MOCK_CUSTOMER_ID) @@ -216,7 +212,7 @@ describe('mutate', () => { }, { mutate_operation_responses: [], - partial_failure_error: Status.fromPartial({ + partial_failure_error: rpc.Status.fromPartial({ details: [ { type_url: `google.ads.googleads.${VERSION}.errors.GoogleAdsFailure`, @@ -326,11 +322,12 @@ describe('mutate', () => { }); it('should return the errors for partial failure', async () => { - const failureMessage = GoogleAdsFailure.fromPartial({ + const failureMessage = errors.GoogleAdsFailure.fromPartial({ errors: [ { - error_code: ErrorCode.fromPartial({ - request_error: RequestErrorEnum_RequestError.BAD_RESOURCE_ID, + error_code: errors.ErrorCode.fromPartial({ + request_error: + errors.RequestErrorEnum_RequestError.BAD_RESOURCE_ID, }), message: 'error message', location: { @@ -345,7 +342,8 @@ describe('mutate', () => { ], }); - const failureBuffer = GoogleAdsFailure.encode(failureMessage).finish(); + const failureBuffer = + errors.GoogleAdsFailure.encode(failureMessage).finish(); const response = await service .setCustomerId(MOCK_CUSTOMER_ID) @@ -357,7 +355,7 @@ describe('mutate', () => { }, { mutate_operation_responses: [], - partial_failure_error: Status.fromPartial({ + partial_failure_error: rpc.Status.fromPartial({ details: [ { type_url: `google.ads.googleads.${VERSION}.errors.GoogleAdsFailure`, diff --git a/tests/protos.spec.ts b/tests/protos.spec.ts index 5f135ad..925c96c 100644 --- a/tests/protos.spec.ts +++ b/tests/protos.spec.ts @@ -1,23 +1,20 @@ import { describe, it, expect } from 'vitest'; -import { ads } from '../src'; +import { services, enums, resources } from '../src'; import { MOCK_ADDRESS, MOCK_CREDENTIALS } from './test-utils'; -const { - services: { CustomerServiceClient, GoogleAdsServiceClient }, - enums: { AdvertisingChannelTypeEnum_AdvertisingChannelType }, - resources: { Campaign }, -} = ads.googleads.v17; - describe('CustomerServiceClient', () => { it('should be exported', () => { - expect(typeof CustomerServiceClient).toBe('function'); + expect(typeof services.CustomerServiceClient).toBe('function'); }); it('should be able to create a client', () => { - const client = new CustomerServiceClient(MOCK_ADDRESS, MOCK_CREDENTIALS); + const client = new services.CustomerServiceClient( + MOCK_ADDRESS, + MOCK_CREDENTIALS, + ); - expect(client).toBeInstanceOf(CustomerServiceClient); + expect(client).toBeInstanceOf(services.CustomerServiceClient); expect(client).toEqual( expect.objectContaining({ @@ -31,13 +28,16 @@ describe('CustomerServiceClient', () => { describe('GoogleAdsServiceClient', () => { it('should be exported', () => { - expect(typeof GoogleAdsServiceClient).toBe('function'); + expect(typeof services.GoogleAdsServiceClient).toBe('function'); }); it('should be able to create a client', () => { - const client = new GoogleAdsServiceClient(MOCK_ADDRESS, MOCK_CREDENTIALS); + const client = new services.GoogleAdsServiceClient( + MOCK_ADDRESS, + MOCK_CREDENTIALS, + ); - expect(client).toBeInstanceOf(GoogleAdsServiceClient); + expect(client).toBeInstanceOf(services.GoogleAdsServiceClient); expect(client).toEqual( expect.objectContaining({ @@ -52,14 +52,14 @@ describe('GoogleAdsServiceClient', () => { }); describe('Campaign', () => { - const campaign = Campaign.fromPartial({ + const campaign = resources.Campaign.fromPartial({ name: 'Planet Express', advertising_channel_type: - AdvertisingChannelTypeEnum_AdvertisingChannelType.SEARCH, + enums.AdvertisingChannelTypeEnum_AdvertisingChannelType.SEARCH, }); it('should be exported', () => { - expect(typeof Campaign).toBe('object'); + expect(typeof resources.Campaign).toBe('object'); }); it('should have a name', () => { @@ -68,7 +68,7 @@ describe('Campaign', () => { it('should have an advertising channel type', () => { expect(campaign.advertising_channel_type).toBe( - AdvertisingChannelTypeEnum_AdvertisingChannelType.SEARCH, + enums.AdvertisingChannelTypeEnum_AdvertisingChannelType.SEARCH, ); }); }); diff --git a/tests/service.spec.ts b/tests/service.spec.ts index 8f3d9d7..e4dd419 100644 --- a/tests/service.spec.ts +++ b/tests/service.spec.ts @@ -5,12 +5,10 @@ import { MOCK_DEVELOPER_TOKEN, MOCK_OAUTH2_CLIENT, } from './test-utils'; -import { ads } from '../src'; +import { services } from '../src'; let service: MockService; -const { GoogleAdsServiceClient } = ads.googleads.v17.services; - beforeAll(async () => { service = new MockService({ auth: MOCK_OAUTH2_CLIENT, @@ -22,7 +20,7 @@ describe('Service', () => { it('should be able to create a service and load client', async () => { const client = service.loadService('GoogleAdsServiceClient'); - expect(client).toBeInstanceOf(GoogleAdsServiceClient); + expect(client).toBeInstanceOf(services.GoogleAdsServiceClient); }); it('should be loaded from cache', async () => { @@ -31,8 +29,8 @@ describe('Service', () => { const cached = service.getCachedClient('GoogleAdsServiceClient'); await Promise.all([ - expect(client).toBeInstanceOf(GoogleAdsServiceClient), - expect(cached).toBeInstanceOf(GoogleAdsServiceClient), + expect(client).toBeInstanceOf(services.GoogleAdsServiceClient), + expect(cached).toBeInstanceOf(services.GoogleAdsServiceClient), ]); }); diff --git a/tests/test-utils.ts b/tests/test-utils.ts index a3b0c07..f96c63c 100644 --- a/tests/test-utils.ts +++ b/tests/test-utils.ts @@ -1,23 +1,7 @@ import { credentials, Metadata, ServiceError } from '@grpc/grpc-js'; import { google } from 'googleapis'; -import { Customer, GoogleAds, MessageType, Service, ads } from '../src'; - -const { - services: { - ListAccessibleCustomersResponse, - GoogleAdsRow, - MutateGoogleAdsRequest, - MutateGoogleAdsResponse, - MutateOperation, - MutateOperationResponse, - SearchGoogleAdsRequest, - SearchGoogleAdsResponse, - SearchGoogleAdsStreamRequest, - SearchGoogleAdsStreamResponse, - }, - enums: { AdGroupStatusEnum_AdGroupStatus, AdGroupTypeEnum_AdGroupType }, -} = ads.googleads.v17; +import { Customer, GoogleAds, Service, enums, services } from '../src'; type AllServices = new (...args: any[]) => any; @@ -41,7 +25,7 @@ export const MOCK_LINKED_CUSTOMER_ID = 'MOCK LINKED CUSTOMER ID'; export const MOCK_CUSTOMERS = ['customers/1234567890']; -export const MOCK_CUSTOMER_CLIENTS: MessageType[] = [ +export const MOCK_CUSTOMER_CLIENTS: services.GoogleAdsRow[] = [ { customer_client: { resource_name: `customers/${MOCK_MANAGER_ID}/customerClients/${MOCK_CUSTOMER_ID}`, @@ -52,28 +36,26 @@ export const MOCK_CUSTOMER_CLIENTS: MessageType[] = [ }, ]; -export const MOCK_CAMPAIGNS: MessageType[] = [ +export const MOCK_CAMPAIGNS: services.GoogleAdsRow[] = [ { campaign: { resource_name: 'customers/1/campaigns/11' } }, { campaign: { resource_name: 'customers/2/campaigns/22' } }, { campaign: { resource_name: 'customers/3/campaigns/33' } }, ]; -export const MOCK_AD_GROUP_OPERATIONS: MessageType[] = [ +export const MOCK_AD_GROUP_OPERATIONS: services.MutateOperation[] = [ { ad_group_operation: { create: { name: 'Ad Group 1', - status: AdGroupStatusEnum_AdGroupStatus.PAUSED, - type: AdGroupTypeEnum_AdGroupType.SEARCH_STANDARD, + status: enums.AdGroupStatusEnum_AdGroupStatus.PAUSED, + type: enums.AdGroupTypeEnum_AdGroupType.SEARCH_STANDARD, campaign: 'customers/1234567890/campaigns/1234567890', }, }, }, ]; -export const MOCK_AD_GROUP_RESULTS: MessageType< - typeof MutateOperationResponse ->[] = [ +export const MOCK_AD_GROUP_RESULTS: services.MutateOperationResponse[] = [ { ad_group_result: { resource_name: 'customers/1/adGroups/11', @@ -96,9 +78,7 @@ export class MockService extends Service { } export class MockCustomer extends Customer { - async mockListAccessibleCustomers(): Promise< - MessageType - > { + async mockListAccessibleCustomers(): Promise { try { return await super.listAccessibleCustomers(); } catch (err: any) { @@ -112,17 +92,17 @@ export class MockCustomer extends Customer { export class MockGoogleAds extends GoogleAds { async mockSearch( - request: MessageType, - mockResponse: MessageType | ServiceError, + request: services.SearchGoogleAdsRequest, + mockResponse: any, metadata?: Metadata | undefined, - ): Promise | ServiceError> { + ): Promise { try { return await super.search(request, metadata); } catch (err: any) { if (err.message === 'Missing customer ID') throw err; } finally { if ('results' in mockResponse) { - return mockResponse as MessageType; + return mockResponse as services.SearchGoogleAdsResponse; } else { throw mockResponse as ServiceError; } @@ -130,14 +110,10 @@ export class MockGoogleAds extends GoogleAds { } async *mockSearchStream( - request: MessageType, - mockResponse: MessageType, + request: services.SearchGoogleAdsStreamRequest, + mockResponse: services.SearchGoogleAdsStreamResponse, metadata?: Metadata | undefined, - ): AsyncGenerator< - MessageType, - any, - unknown - > { + ): AsyncGenerator { const stream = super.searchStream(request, metadata); try { @@ -153,10 +129,10 @@ export class MockGoogleAds extends GoogleAds { } async mockMutate( - request: MessageType, - mockResponse: MessageType, + request: services.MutateGoogleAdsRequest, + mockResponse: services.MutateGoogleAdsResponse, metadata?: Metadata | undefined, - ): Promise> { + ): Promise { try { return await super.mutate(request, metadata); } catch (err: any) {