Skip to content

Commit

Permalink
feat: Errors from gax layer (#1090)
Browse files Browse the repository at this point in the history
* Getting grpc set up for tests

* test for error sent through gax

* Trying other things

* Group everything into describe blocks.

* Add Google header

* Added more tests

* Create mock service files

* refactor a check

* mock server tests

* undo tests

* Pass metadata through

* build fix

* work in progress

* Add service error check and change test

* Remove TODO and add done hook.

* Logs for debugging

* use the tcp-port-used library instead

* use await

Co-authored-by: Benjamin E. Coe <bencoe@google.com>
  • Loading branch information
danieljbruce and bcoe committed Jul 4, 2022
1 parent b997a6b commit ecae5f3
Show file tree
Hide file tree
Showing 6 changed files with 338 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -87,6 +87,7 @@
"pack-n-play": "^1.0.0-2",
"proxyquire": "^2.0.0",
"sinon": "^14.0.0",
"tcp-port-used": "^1.0.2",
"snap-shot-it": "^7.9.1",
"ts-loader": "^9.0.0",
"typescript": "^4.6.4",
Expand Down
63 changes: 63 additions & 0 deletions src/util/mock-servers/mock-server.ts
@@ -0,0 +1,63 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ** This file is automatically generated by gapic-generator-typescript. **
// ** https://github.com/googleapis/gapic-generator-typescript **
// ** All changes to this file may be overwritten. **

import {grpc} from 'google-gax';

const DEFAULT_PORT = 1234;

export class MockServer {
port: string;
services: Set<grpc.ServiceDefinition> = new Set();
server: grpc.Server;

constructor(
callback?: (port: string) => void,
port?: string | number | undefined
) {
const portString = Number(port ? port : DEFAULT_PORT).toString();
this.port = portString;
const server = new grpc.Server();
this.server = server;
server.bindAsync(
`localhost:${this.port}`,
grpc.ServerCredentials.createInsecure(),
() => {
server.start();
callback ? callback(portString) : undefined;
}
);
}

setService(
service: grpc.ServiceDefinition,
implementation: grpc.UntypedServiceImplementation
) {
if (this.services.has(service)) {
this.server.removeService(service);
} else {
this.services.add(service);
}
this.server.addService(service, implementation);
}

shutdown(callback: (err?: Error) => void) {
this.server.tryShutdown((err?: Error) => {
callback(err);
});
}
}
34 changes: 34 additions & 0 deletions src/util/mock-servers/mock-service.ts
@@ -0,0 +1,34 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ** This file is automatically generated by gapic-generator-typescript. **
// ** https://github.com/googleapis/gapic-generator-typescript **
// ** All changes to this file may be overwritten. **

import {grpc} from 'google-gax';

import {MockServer} from './mock-server';

export abstract class MockService {
abstract service: grpc.ServiceDefinition;
server: MockServer;

constructor(server: MockServer) {
this.server = server;
}

setService(implementation: grpc.UntypedServiceImplementation) {
this.server.setService(this.service, implementation);
}
}
@@ -0,0 +1,38 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ** This file is automatically generated by gapic-generator-typescript. **
// ** https://github.com/googleapis/gapic-generator-typescript **
// ** All changes to this file may be overwritten. **

import grpc = require('@grpc/grpc-js');
import jsonProtos = require('../../../../protos/protos.json');
import protoLoader = require('@grpc/proto-loader');
import {MockService} from '../mock-service';

const packageDefinition = protoLoader.fromJSON(jsonProtos, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const proto = grpc.loadPackageDefinition(packageDefinition);

export class BigtableClientMockService extends MockService {
service =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
proto.google.bigtable.v2.Bigtable.service;
}
151 changes: 151 additions & 0 deletions test/errors.ts
@@ -0,0 +1,151 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ** This file is automatically generated by gapic-generator-typescript. **
// ** https://github.com/googleapis/gapic-generator-typescript **
// ** All changes to this file may be overwritten. **

import {before, describe, it} from 'mocha';
import {Bigtable} from '../src';
import * as assert from 'assert';

import {GoogleError, grpc, ServiceError} from 'google-gax';
import {MockServer} from '../src/util/mock-servers/mock-server';
import {BigtableClientMockService} from '../src/util/mock-servers/service-implementations/bigtable-client-mock-service';
import {MockService} from '../src/util/mock-servers/mock-service';

function isServiceError(error: any): error is ServiceError {
return (
error.code !== undefined &&
error.details !== undefined &&
error.metadata !== undefined
);
}

describe('Bigtable/Errors', () => {
let server: MockServer;
let bigtable: Bigtable;
let table: any;

before(done => {
server = new MockServer(() => {
bigtable = new Bigtable({
apiEndpoint: `localhost:${server.port}`,
});
table = bigtable.instance('fake-instance').table('fake-table');
done();
});
});

describe('with the bigtable data client', () => {
let service: MockService;
before(async () => {
service = new BigtableClientMockService(server);
});

describe('sends errors through a streaming request', () => {
const errorDetails =
'Table not found: projects/my-project/instances/my-instance/tables/my-table';
const emitTableNotExistsError = (stream: any) => {
// TODO: Replace stream with type
const metadata = new grpc.Metadata();
metadata.set(
'grpc-server-stats-bin',
Buffer.from([0, 0, 116, 73, 159, 3, 0, 0, 0, 0])
);
stream.emit('error', {
code: 5,
details: errorDetails,
metadata,
});
};
function checkTableNotExistError(err: any) {
if (isServiceError(err)) {
const {code, message, details} = err;
assert.strictEqual(details, errorDetails);
assert.strictEqual(code, 5);
assert.strictEqual(message, `5 NOT_FOUND: ${errorDetails}`);
} else {
assert.fail(
'Errors checked using this function should all be GoogleErrors'
);
}
}
describe('with ReadRows service', () => {
before(async () => {
service.setService({
ReadRows: emitTableNotExistsError,
});
});
it('should produce human readable error when passing through gax', done => {
const readStream = table.createReadStream({});
readStream.on('error', (err: GoogleError) => {
checkTableNotExistError(err);
done();
});
});
});
describe('with mutateRows service through insert', () => {
before(async () => {
service.setService({
mutateRows: emitTableNotExistsError,
});
});
it('should produce human readable error when passing through gax', async () => {
const timestamp = new Date();
const rowsToInsert = [
{
key: 'r2',
data: {
cf1: {
c1: {
value: 'test-value2',
labels: [],
timestamp,
},
},
},
},
];
try {
await table.insert(rowsToInsert);
} catch (err) {
checkTableNotExistError(err);
return;
}
assert.fail('An error should have been thrown by the stream');
});
});
describe('with sampleRowKeys', () => {
before(async () => {
service.setService({
sampleRowKeys: emitTableNotExistsError,
});
});
it('should produce human readable error when passing through gax', async () => {
try {
await table.sampleRowKeys({});
} catch (err) {
checkTableNotExistError(err);
return;
}
assert.fail('An error should have been thrown by the stream');
});
});
});
});
after(async () => {
server.shutdown(() => {});
});
});
51 changes: 51 additions & 0 deletions test/util/mock-server.ts
@@ -0,0 +1,51 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ** This file is automatically generated by gapic-generator-typescript. **
// ** https://github.com/googleapis/gapic-generator-typescript **
// ** All changes to this file may be overwritten. **

import {describe, it} from 'mocha';
import {MockServer} from '../../src/util/mock-servers/mock-server';
import * as assert from 'assert';

const tcpPortUsed = require('tcp-port-used');

describe('Bigtable/Mock-Server', () => {
const inputPort = '1234';
let server: MockServer;
async function checkPort(port: string, inUse: boolean, callback: () => void) {
const isInUse: boolean = await tcpPortUsed.check(
parseInt(port),
'localhost'
);
assert.strictEqual(isInUse, inUse);
callback();
}
describe('Ensure server shuts down properly when destroyed', () => {
it('should start a mock server', done => {
server = new MockServer(port => {
checkPort(port, true, done);
}, inputPort);
});
});
after(done => {
checkPort(server.port, true, () => {
server.shutdown((err?: Error) => {
assert.deepStrictEqual(err, undefined);
checkPort(server.port, false, done);
});
});
});
});

0 comments on commit ecae5f3

Please sign in to comment.