Skip to content

Commit ecae5f3

Browse files
danieljbrucebcoe
andauthored
feat: Errors from gax layer (#1090)
* 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>
1 parent b997a6b commit ecae5f3

File tree

6 files changed

+338
-0
lines changed

6 files changed

+338
-0
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"pack-n-play": "^1.0.0-2",
8888
"proxyquire": "^2.0.0",
8989
"sinon": "^14.0.0",
90+
"tcp-port-used": "^1.0.2",
9091
"snap-shot-it": "^7.9.1",
9192
"ts-loader": "^9.0.0",
9293
"typescript": "^4.6.4",

src/util/mock-servers/mock-server.ts

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// ** This file is automatically generated by gapic-generator-typescript. **
16+
// ** https://github.com/googleapis/gapic-generator-typescript **
17+
// ** All changes to this file may be overwritten. **
18+
19+
import {grpc} from 'google-gax';
20+
21+
const DEFAULT_PORT = 1234;
22+
23+
export class MockServer {
24+
port: string;
25+
services: Set<grpc.ServiceDefinition> = new Set();
26+
server: grpc.Server;
27+
28+
constructor(
29+
callback?: (port: string) => void,
30+
port?: string | number | undefined
31+
) {
32+
const portString = Number(port ? port : DEFAULT_PORT).toString();
33+
this.port = portString;
34+
const server = new grpc.Server();
35+
this.server = server;
36+
server.bindAsync(
37+
`localhost:${this.port}`,
38+
grpc.ServerCredentials.createInsecure(),
39+
() => {
40+
server.start();
41+
callback ? callback(portString) : undefined;
42+
}
43+
);
44+
}
45+
46+
setService(
47+
service: grpc.ServiceDefinition,
48+
implementation: grpc.UntypedServiceImplementation
49+
) {
50+
if (this.services.has(service)) {
51+
this.server.removeService(service);
52+
} else {
53+
this.services.add(service);
54+
}
55+
this.server.addService(service, implementation);
56+
}
57+
58+
shutdown(callback: (err?: Error) => void) {
59+
this.server.tryShutdown((err?: Error) => {
60+
callback(err);
61+
});
62+
}
63+
}

src/util/mock-servers/mock-service.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// ** This file is automatically generated by gapic-generator-typescript. **
16+
// ** https://github.com/googleapis/gapic-generator-typescript **
17+
// ** All changes to this file may be overwritten. **
18+
19+
import {grpc} from 'google-gax';
20+
21+
import {MockServer} from './mock-server';
22+
23+
export abstract class MockService {
24+
abstract service: grpc.ServiceDefinition;
25+
server: MockServer;
26+
27+
constructor(server: MockServer) {
28+
this.server = server;
29+
}
30+
31+
setService(implementation: grpc.UntypedServiceImplementation) {
32+
this.server.setService(this.service, implementation);
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// ** This file is automatically generated by gapic-generator-typescript. **
16+
// ** https://github.com/googleapis/gapic-generator-typescript **
17+
// ** All changes to this file may be overwritten. **
18+
19+
import grpc = require('@grpc/grpc-js');
20+
import jsonProtos = require('../../../../protos/protos.json');
21+
import protoLoader = require('@grpc/proto-loader');
22+
import {MockService} from '../mock-service';
23+
24+
const packageDefinition = protoLoader.fromJSON(jsonProtos, {
25+
keepCase: true,
26+
longs: String,
27+
enums: String,
28+
defaults: true,
29+
oneofs: true,
30+
});
31+
const proto = grpc.loadPackageDefinition(packageDefinition);
32+
33+
export class BigtableClientMockService extends MockService {
34+
service =
35+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
36+
// @ts-ignore
37+
proto.google.bigtable.v2.Bigtable.service;
38+
}

test/errors.ts

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// ** This file is automatically generated by gapic-generator-typescript. **
16+
// ** https://github.com/googleapis/gapic-generator-typescript **
17+
// ** All changes to this file may be overwritten. **
18+
19+
import {before, describe, it} from 'mocha';
20+
import {Bigtable} from '../src';
21+
import * as assert from 'assert';
22+
23+
import {GoogleError, grpc, ServiceError} from 'google-gax';
24+
import {MockServer} from '../src/util/mock-servers/mock-server';
25+
import {BigtableClientMockService} from '../src/util/mock-servers/service-implementations/bigtable-client-mock-service';
26+
import {MockService} from '../src/util/mock-servers/mock-service';
27+
28+
function isServiceError(error: any): error is ServiceError {
29+
return (
30+
error.code !== undefined &&
31+
error.details !== undefined &&
32+
error.metadata !== undefined
33+
);
34+
}
35+
36+
describe('Bigtable/Errors', () => {
37+
let server: MockServer;
38+
let bigtable: Bigtable;
39+
let table: any;
40+
41+
before(done => {
42+
server = new MockServer(() => {
43+
bigtable = new Bigtable({
44+
apiEndpoint: `localhost:${server.port}`,
45+
});
46+
table = bigtable.instance('fake-instance').table('fake-table');
47+
done();
48+
});
49+
});
50+
51+
describe('with the bigtable data client', () => {
52+
let service: MockService;
53+
before(async () => {
54+
service = new BigtableClientMockService(server);
55+
});
56+
57+
describe('sends errors through a streaming request', () => {
58+
const errorDetails =
59+
'Table not found: projects/my-project/instances/my-instance/tables/my-table';
60+
const emitTableNotExistsError = (stream: any) => {
61+
// TODO: Replace stream with type
62+
const metadata = new grpc.Metadata();
63+
metadata.set(
64+
'grpc-server-stats-bin',
65+
Buffer.from([0, 0, 116, 73, 159, 3, 0, 0, 0, 0])
66+
);
67+
stream.emit('error', {
68+
code: 5,
69+
details: errorDetails,
70+
metadata,
71+
});
72+
};
73+
function checkTableNotExistError(err: any) {
74+
if (isServiceError(err)) {
75+
const {code, message, details} = err;
76+
assert.strictEqual(details, errorDetails);
77+
assert.strictEqual(code, 5);
78+
assert.strictEqual(message, `5 NOT_FOUND: ${errorDetails}`);
79+
} else {
80+
assert.fail(
81+
'Errors checked using this function should all be GoogleErrors'
82+
);
83+
}
84+
}
85+
describe('with ReadRows service', () => {
86+
before(async () => {
87+
service.setService({
88+
ReadRows: emitTableNotExistsError,
89+
});
90+
});
91+
it('should produce human readable error when passing through gax', done => {
92+
const readStream = table.createReadStream({});
93+
readStream.on('error', (err: GoogleError) => {
94+
checkTableNotExistError(err);
95+
done();
96+
});
97+
});
98+
});
99+
describe('with mutateRows service through insert', () => {
100+
before(async () => {
101+
service.setService({
102+
mutateRows: emitTableNotExistsError,
103+
});
104+
});
105+
it('should produce human readable error when passing through gax', async () => {
106+
const timestamp = new Date();
107+
const rowsToInsert = [
108+
{
109+
key: 'r2',
110+
data: {
111+
cf1: {
112+
c1: {
113+
value: 'test-value2',
114+
labels: [],
115+
timestamp,
116+
},
117+
},
118+
},
119+
},
120+
];
121+
try {
122+
await table.insert(rowsToInsert);
123+
} catch (err) {
124+
checkTableNotExistError(err);
125+
return;
126+
}
127+
assert.fail('An error should have been thrown by the stream');
128+
});
129+
});
130+
describe('with sampleRowKeys', () => {
131+
before(async () => {
132+
service.setService({
133+
sampleRowKeys: emitTableNotExistsError,
134+
});
135+
});
136+
it('should produce human readable error when passing through gax', async () => {
137+
try {
138+
await table.sampleRowKeys({});
139+
} catch (err) {
140+
checkTableNotExistError(err);
141+
return;
142+
}
143+
assert.fail('An error should have been thrown by the stream');
144+
});
145+
});
146+
});
147+
});
148+
after(async () => {
149+
server.shutdown(() => {});
150+
});
151+
});

test/util/mock-server.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
// ** This file is automatically generated by gapic-generator-typescript. **
16+
// ** https://github.com/googleapis/gapic-generator-typescript **
17+
// ** All changes to this file may be overwritten. **
18+
19+
import {describe, it} from 'mocha';
20+
import {MockServer} from '../../src/util/mock-servers/mock-server';
21+
import * as assert from 'assert';
22+
23+
const tcpPortUsed = require('tcp-port-used');
24+
25+
describe('Bigtable/Mock-Server', () => {
26+
const inputPort = '1234';
27+
let server: MockServer;
28+
async function checkPort(port: string, inUse: boolean, callback: () => void) {
29+
const isInUse: boolean = await tcpPortUsed.check(
30+
parseInt(port),
31+
'localhost'
32+
);
33+
assert.strictEqual(isInUse, inUse);
34+
callback();
35+
}
36+
describe('Ensure server shuts down properly when destroyed', () => {
37+
it('should start a mock server', done => {
38+
server = new MockServer(port => {
39+
checkPort(port, true, done);
40+
}, inputPort);
41+
});
42+
});
43+
after(done => {
44+
checkPort(server.port, true, () => {
45+
server.shutdown((err?: Error) => {
46+
assert.deepStrictEqual(err, undefined);
47+
checkPort(server.port, false, done);
48+
});
49+
});
50+
});
51+
});

0 commit comments

Comments
 (0)