Skip to content

Commit

Permalink
chore(testproxy): Change the checkAndMutateRow test proxy service so …
Browse files Browse the repository at this point in the history
…that it uses the handwritten layer (#1541)

* Build the request in a separate function

* Apply the right mocks to the tests

* Setup the frame for the new timeout test

* Make sure to return the stream

* Add assert checks - call count is 11

* Revert "Add assert checks - call count is 11"

This reverts commit 24e3e33.

* Revert "Make sure to return the stream"

This reverts commit 768242a.

* Revert "Setup the frame for the new timeout test"

This reverts commit f854546.

* Try to generate inverses

* More code trying to invert the mutations

* Delete files that are not needed anymore

* Create a method for mutate.parse inverses

* Create a method for createFlatMutationsListWithFn

* Omit AppProfileId from call

* Add a TODO for the other mutation types

* Rename the file

* Should invert mutations properly

* Create some copy/pasted functions and a blank file

* Remove rowKey from the inverse function

* Remove unused row key

* Rename mutations to entries

* Create tests for the flatten mutations code

* Add comment

* Remove unused code

* Set up the test proxy to use the handwritten layer

* Fix the require errors

* Remove a known failure that has been addressed

* Add a test to ensure Gapic request is preserved

* Remove some unused imports

* Remove only everywhere

* revert changes in the source file

* Remove the mocks from the test file

* This file does not need to live here anymore.

* Add headers

* Clarify the TODO

* Simplify the variable name

* Generate documentation for handwrittenLayerMutations

* Address td-ignore statements

* Use the callback provided to the Gapic layer

* Remove onlys

* Don’t modify mutation in the tests

* Add a known failure

* Add another known failure

* Added more TestCheckAndMutateRow failures

* Indent this code to indicate it is separate

* Move inside the block

* Refactor out the clientId string

* Function renames

* Prefer inline variable name

* Better variable names

* Eliminate use of any

* Eliminate intermediate variable

* Eliminate a line of code. Make variable inline.

* Inline another variable

* Shorten another test

* Remove unused import

* Shorten imports

* Revert "Shorten imports"

This reverts commit 4bb1bbe.

* Revert "Remove unused import"

This reverts commit 554ba8d.

* Revert "Revert "Remove unused import""

This reverts commit b14f66f.

* Use service appProfileId by default

* Eliminate large comment block

* Address the TODOs - remove them

* linter

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* Remove test

* Add js suffix

* Compile the test proxy folder

* Add all testproxy typescript files to compiler

---------

Co-authored-by: Kevin Kim <[email protected]>
Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 18, 2024
1 parent 7f1099a commit 86f99f5
Show file tree
Hide file tree
Showing 9 changed files with 479 additions and 27 deletions.
4 changes: 2 additions & 2 deletions test/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,10 +494,10 @@ describe('Bigtable/Mutation', () => {
data: [],
};
const mutation = new Mutation(data);
Mutation.encodeSetCell = _data => {
sandbox.stub(Mutation, 'encodeSetCell').callsFake(_data => {
assert.strictEqual(_data, data.data);
return fakeEncoded;
};
});
const mutationProto = mutation.toProto();
assert.strictEqual(mutationProto.mutations, fakeEncoded);
assert.strictEqual(mutationProto.rowKey, data.key);
Expand Down
159 changes: 159 additions & 0 deletions test/test-proxy/checkAndMutateRowService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright 2024 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.

import * as assert from 'assert';

import {describe} from 'mocha';
import {protos} from '../../src';
import {BigtableClient} from '../../src/v2';
import type {Callback, CallOptions, ServiceError} from 'google-gax';
const checkAndMutateRowService = require('../../../testproxy/services/check-and-mutate-row.js');
const createClient = require('../../../testproxy/services/create-client.js');

describe('TestProxy/CheckAndMutateRow', () => {
const testCases: protos.google.bigtable.v2.ICheckAndMutateRowRequest[] = [
{
tableName: 'projects/projectId/instances/instance/tables/test-table',
appProfileId: 'test-app-profile',
rowKey: Buffer.from('test-row-key'),
predicateFilter: null,
trueMutations: [
{
setCell: {
familyName: 'cf1',
timestampMicros: 1000007,
columnQualifier: Buffer.from('cq1'),
value: Buffer.from('value1'),
},
},
{
setCell: {
familyName: 'cf2',
timestampMicros: 1000007,
columnQualifier: Buffer.from('cq2'),
value: Buffer.from('value2'),
},
},
],
falseMutations: [
{
setCell: {
familyName: 'cf1',
timestampMicros: 1000007,
columnQualifier: Buffer.from('cq1'),
value: Buffer.from('value1'),
},
},
{
setCell: {
familyName: 'cf2',
timestampMicros: 1000007,
columnQualifier: Buffer.from('cq2'),
value: Buffer.from('value2'),
},
},
],
},
];
describe('Ensure the proper request is passed to the Gapic Layer', () => {
const clientId = 'TestCheckAndMutateRow_NoRetry_TransientError';
testCases.forEach((checkAndMutateRowRequest, index) => {
it(`Run test ${index}`, done => {
(async () => {
const clientMap = new Map();
const createClientFunction = createClient({clientMap});
await new Promise((resolve, reject) => {
createClientFunction(
{
request: {
clientId,
dataTarget: 'localhost:1234',
projectId: 'projectId',
instanceId: 'instance',
appProfileId: '',
},
},
(error: ServiceError, response: {}) => {
if (error) {
reject(error);
}
resolve(response);
}
);
});
{
// Mock out the Gapic layer so we can see requests coming into it
const bigtable = clientMap.get(clientId);
const bigtableClient = new BigtableClient(
bigtable.options.BigtableClient
);
bigtable.api['BigtableClient'] = bigtableClient;
bigtableClient.checkAndMutateRow = (
request?: protos.google.bigtable.v2.ICheckAndMutateRowRequest,
optionsOrCallback?:
| CallOptions
| Callback<
protos.google.bigtable.v2.ICheckAndMutateRowResponse,
| protos.google.bigtable.v2.ICheckAndMutateRowRequest
| null
| undefined,
{} | null | undefined
>,
callback?: Callback<
protos.google.bigtable.v2.ICheckAndMutateRowResponse,
| protos.google.bigtable.v2.ICheckAndMutateRowRequest
| null
| undefined,
{} | null | undefined
>
) => {
try {
// If the Gapic request is correct then the test passes.
assert.deepStrictEqual(request, checkAndMutateRowRequest);
} catch (e) {
// If the Gapic request is incorrect then the test fails with an error.
done(e);
}
if (callback) {
callback(null, {});
}
return new Promise(resolve => {
const response: protos.google.bigtable.v2.ICheckAndMutateRowResponse =
{};
resolve([response, {}, undefined]);
});
};
}
await new Promise((resolve, reject) => {
checkAndMutateRowService({clientMap})(
{
request: {
clientId,
request: checkAndMutateRowRequest,
},
},
(error: ServiceError, response: {}) => {
if (error) {
reject(error);
}
resolve(response);
}
);
});
done();
})();
});
});
});
});
46 changes: 46 additions & 0 deletions test/test-proxy/mutationParseInverse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2024 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.

import * as assert from 'assert';
import {Mutation} from '../../src/mutation';
import {mutationParseInverse} from '../../testproxy/services/utils/request/mutateInverse';

describe('Check mutation parse and mutationParseInverse are inverses', () => {
it('should invert mutations properly', () => {
const gapicLayerRequest = {
mutations: [
{
setCell: {
familyName: 'cf1',
timestampMicros: 1000007,
columnQualifier: Buffer.from('cq1'),
value: Buffer.from('value1'),
},
},
{
setCell: {
familyName: 'cf2',
timestampMicros: 1000007,
columnQualifier: Buffer.from('cq2'),
value: Buffer.from('value2'),
},
},
],
};
assert.deepStrictEqual(
Mutation.parse(mutationParseInverse(gapicLayerRequest)),
gapicLayerRequest
);
});
});
24 changes: 22 additions & 2 deletions test/test-proxy/readModifyWriteRowService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as assert from 'assert';
import {describe} from 'mocha';
import {protos} from '../../src';
import {BigtableClient} from '../../src/v2';
import type {Callback, CallOptions} from 'google-gax';
const readModifyWriteRowService = require('../../../testproxy/services/read-modify-write-row.js');
const createClient = require('../../../testproxy/services/create-client.js');

Expand Down Expand Up @@ -74,16 +75,34 @@ describe('TestProxy/ReadModifyWriteRow', () => {
);
bigtable.api['BigtableClient'] = bigtableClient;
bigtableClient.readModifyWriteRow = (
request?: protos.google.bigtable.v2.IReadModifyWriteRowRequest
request?: protos.google.bigtable.v2.IReadModifyWriteRowRequest,
optionsOrCallback?:
| CallOptions
| Callback<
protos.google.bigtable.v2.IReadModifyWriteRowResponse,
| protos.google.bigtable.v2.IReadModifyWriteRowRequest
| null
| undefined,
{} | null | undefined
>,
callback?: Callback<
protos.google.bigtable.v2.IReadModifyWriteRowResponse,
| protos.google.bigtable.v2.IReadModifyWriteRowRequest
| null
| undefined,
{} | null | undefined
>
) => {
try {
// If the Gapic request is correct then the test passes.
assert.deepStrictEqual(request, readModifyWriteRowRequest);
done();
} catch (e) {
// If the Gapic request is incorrect then the test fails with an error.
done(e);
}
if (callback) {
callback(null, {});
}
return new Promise(resolve => {
const response: protos.google.bigtable.v2.IReadModifyWriteRowResponse =
{};
Expand All @@ -109,6 +128,7 @@ describe('TestProxy/ReadModifyWriteRow', () => {
}
);
});
done();
})();
});
});
Expand Down
6 changes: 5 additions & 1 deletion testproxy/known_failures.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ TestReadRows_Retry_WithRoutingCookie\|
TestReadRows_Retry_WithRoutingCookie_MultipleErrorResponses\|
TestReadRows_Retry_WithRetryInfo\|
TestReadRows_Retry_WithRetryInfo_MultipleErrorResponse\|
TestCheckAndMutateRow_NoRetry_TransientError\|
TestCheckAndMutateRow_Generic_DeadlineExceeded\|
TestCheckAndMutateRow_Generic_Headers\|
TestCheckAndMutateRow_NoRetry_TrueMutations\|
TestCheckAndMutateRow_NoRetry_FalseMutations\|
TestCheckAndMutateRow_Generic_CloseClient\|
TestCheckAndMutateRow_Generic_MultiStreams\|
TestSampleRowKeys_Generic_DeadlineExceeded\|
TestSampleRowKeys_Retry_WithRoutingCookie
93 changes: 72 additions & 21 deletions testproxy/services/check-and-mutate-row.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,83 @@
const grpc = require('@grpc/grpc-js');

const normalizeCallback = require('./utils/normalize-callback.js');
const getTableInfo = require('./utils/get-table-info');
const {
createFlatMutationsListWithFnInverse,
} = require('../../build/testproxy/services/utils/request/createFlatMutationsList.js');
const {
mutationParseInverse,
} = require('../../build/testproxy/services/utils/request/mutateInverse.js');

const v2 = Symbol.for('v2');
/**
* Transforms mutations from the gRPC layer format to the handwritten layer format.
* This function takes an array of gRPC layer mutations (google.bigtable.v2.IMutation[]) and converts
* them back to the format used by the handwritten layer. It essentially reverses the transformation
* performed by Mutation.parse. It's used internally by the test proxy for checkAndMutateRow.
*
* @param {google.bigtable.v2.IMutation[]} gapicLayerMutations An array of mutations in the gRPC layer format.
* @returns {FilterConfigOption[]} An array of mutations in the handwritten layer format.
*/
function handwrittenLayerMutations(gapicLayerMutations) {
return createFlatMutationsListWithFnInverse(
[
{
mutations: gapicLayerMutations,
},
],
mutationParseInverse,
1
);
}

/**
* Converts a byte array (or string) back to a string. This is the inverse of
* Mutation.convertToBytes.
*
* @param {Bytes} bytes The byte array or string to convert.
* @returns {string} The converted string.
*/
function convertFromBytes(bytes) {
if (bytes instanceof Buffer) {
return bytes.toString();
} else if (typeof bytes === 'string') {
return bytes;
} else {
throw new Error('Invalid input type. Must be Buffer or string.');
}
}

const checkAndMutateRow = ({clientMap}) =>
normalizeCallback(async rawRequest => {
const {request} = rawRequest;
const {request: checkAndMutateRequest} = request;
const {falseMutations, rowKey, tableName, trueMutations} =
checkAndMutateRequest;

const {clientId} = request;
const client = clientMap.get(clientId)[v2];
const appProfileId = clientMap.get(clientId).appProfileId;

const [result] = await client.checkAndMutateRow({
appProfileId,
falseMutations,
rowKey,
tableName,
trueMutations,
});

return {
status: {code: grpc.status.OK, details: []},
result,
};
const {clientId, request: checkAndMutateRowRequest} = request;
const {appProfileId, falseMutations, rowKey, tableName, trueMutations} =
checkAndMutateRowRequest;
const onMatch = handwrittenLayerMutations(trueMutations);
const onNoMatch = handwrittenLayerMutations(falseMutations);
const id = convertFromBytes(rowKey);
const bigtable = clientMap.get(clientId);
bigtable.appProfileId =
appProfileId === '' ? clientMap.get(clientId).appProfileId : appProfileId;
const table = getTableInfo(bigtable, tableName);
const row = table.row(id);
const filter = [];
const filterConfig = {onMatch, onNoMatch};
try {
const result = await row.filter(filter, filterConfig);
return {
status: {code: grpc.status.OK, details: []},
row: result,
};
} catch (e) {
return {
status: {
code: e.code,
details: [],
message: e.message,
},
};
}
});

module.exports = checkAndMutateRow;
Loading

0 comments on commit 86f99f5

Please sign in to comment.