grpc-node/packages/grpc-js/test/test-metadata.ts

331 lines
10 KiB
TypeScript

/*
* Copyright 2019 gRPC authors.
*
* 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
*
* http://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 * as http2 from 'http2';
import { range } from 'lodash';
import { Metadata, MetadataObject, MetadataValue } from '../src/metadata';
class TestMetadata extends Metadata {
getInternalRepresentation() {
return this.internalRepr;
}
static fromHttp2Headers(headers: http2.IncomingHttpHeaders): TestMetadata {
const result = Metadata.fromHttp2Headers(headers) as TestMetadata;
result.getInternalRepresentation =
TestMetadata.prototype.getInternalRepresentation;
return result;
}
}
const validKeyChars = '0123456789abcdefghijklmnopqrstuvwxyz_-.';
const validNonBinValueChars = range(0x20, 0x7f)
.map(code => String.fromCharCode(code))
.join('');
describe('Metadata', () => {
let metadata: TestMetadata;
beforeEach(() => {
metadata = new TestMetadata();
});
describe('set', () => {
it('Only accepts string values for non "-bin" keys', () => {
assert.throws(() => {
metadata.set('key', Buffer.from('value'));
});
assert.doesNotThrow(() => {
metadata.set('key', 'value');
});
});
it('Only accepts Buffer values for "-bin" keys', () => {
assert.throws(() => {
metadata.set('key-bin', 'value');
});
assert.doesNotThrow(() => {
metadata.set('key-bin', Buffer.from('value'));
});
});
it('Rejects invalid keys', () => {
assert.doesNotThrow(() => {
metadata.set(validKeyChars, 'value');
});
assert.throws(() => {
metadata.set('key$', 'value');
}, /Error: Metadata key "key\$" contains illegal characters/);
assert.throws(() => {
metadata.set('', 'value');
});
});
it('Rejects values with non-ASCII characters', () => {
assert.doesNotThrow(() => {
metadata.set('key', validNonBinValueChars);
});
assert.throws(() => {
metadata.set('key', 'résumé');
});
});
it('Saves values that can be retrieved', () => {
metadata.set('key', 'value');
assert.deepStrictEqual(metadata.get('key'), ['value']);
});
it('Overwrites previous values', () => {
metadata.set('key', 'value1');
metadata.set('key', 'value2');
assert.deepStrictEqual(metadata.get('key'), ['value2']);
});
it('Normalizes keys', () => {
metadata.set('Key', 'value1');
assert.deepStrictEqual(metadata.get('key'), ['value1']);
metadata.set('KEY', 'value2');
assert.deepStrictEqual(metadata.get('key'), ['value2']);
});
});
describe('add', () => {
it('Only accepts string values for non "-bin" keys', () => {
assert.throws(() => {
metadata.add('key', Buffer.from('value'));
});
assert.doesNotThrow(() => {
metadata.add('key', 'value');
});
});
it('Only accepts Buffer values for "-bin" keys', () => {
assert.throws(() => {
metadata.add('key-bin', 'value');
});
assert.doesNotThrow(() => {
metadata.add('key-bin', Buffer.from('value'));
});
});
it('Rejects invalid keys', () => {
assert.throws(() => {
metadata.add('key$', 'value');
});
assert.throws(() => {
metadata.add('', 'value');
});
});
it('Saves values that can be retrieved', () => {
metadata.add('key', 'value');
assert.deepStrictEqual(metadata.get('key'), ['value']);
});
it('Combines with previous values', () => {
metadata.add('key', 'value1');
metadata.add('key', 'value2');
assert.deepStrictEqual(metadata.get('key'), ['value1', 'value2']);
});
it('Normalizes keys', () => {
metadata.add('Key', 'value1');
assert.deepStrictEqual(metadata.get('key'), ['value1']);
metadata.add('KEY', 'value2');
assert.deepStrictEqual(metadata.get('key'), ['value1', 'value2']);
});
});
describe('remove', () => {
it('clears values from a key', () => {
metadata.add('key', 'value');
metadata.remove('key');
assert.deepStrictEqual(metadata.get('key'), []);
});
it('Normalizes keys', () => {
metadata.add('key', 'value');
metadata.remove('KEY');
assert.deepStrictEqual(metadata.get('key'), []);
});
});
describe('get', () => {
beforeEach(() => {
metadata.add('key', 'value1');
metadata.add('key', 'value2');
metadata.add('key-bin', Buffer.from('value'));
});
it('gets all values associated with a key', () => {
assert.deepStrictEqual(metadata.get('key'), ['value1', 'value2']);
});
it('Normalizes keys', () => {
assert.deepStrictEqual(metadata.get('KEY'), ['value1', 'value2']);
});
it('returns an empty list for non-existent keys', () => {
assert.deepStrictEqual(metadata.get('non-existent-key'), []);
});
it('returns Buffers for "-bin" keys', () => {
assert.ok(metadata.get('key-bin')[0] instanceof Buffer);
});
});
describe('getMap', () => {
it('gets a map of keys to values', () => {
metadata.add('key1', 'value1');
metadata.add('Key2', 'value2');
metadata.add('KEY3', 'value3a');
metadata.add('KEY3', 'value3b');
assert.deepStrictEqual(metadata.getMap(), {
key1: 'value1',
key2: 'value2',
key3: 'value3a',
});
});
});
describe('clone', () => {
it('retains values from the original', () => {
metadata.add('key', 'value');
const copy = metadata.clone();
assert.deepStrictEqual(copy.get('key'), ['value']);
});
it('Does not see newly added values', () => {
metadata.add('key', 'value1');
const copy = metadata.clone();
metadata.add('key', 'value2');
assert.deepStrictEqual(copy.get('key'), ['value1']);
});
it('Does not add new values to the original', () => {
metadata.add('key', 'value1');
const copy = metadata.clone();
copy.add('key', 'value2');
assert.deepStrictEqual(metadata.get('key'), ['value1']);
});
it('Copy cannot modify binary values in the original', () => {
const buf = Buffer.from('value-bin');
metadata.add('key-bin', buf);
const copy = metadata.clone();
const copyBuf = copy.get('key-bin')[0] as Buffer;
assert.deepStrictEqual(copyBuf, buf);
copyBuf.fill(0);
assert.notDeepStrictEqual(copyBuf, buf);
});
});
describe('merge', () => {
it('appends values from a given metadata object', () => {
metadata.add('key1', 'value1');
metadata.add('Key2', 'value2a');
metadata.add('KEY3', 'value3a');
metadata.add('key4', 'value4');
const metadata2 = new TestMetadata();
metadata2.add('KEY1', 'value1');
metadata2.add('key2', 'value2b');
metadata2.add('key3', 'value3b');
metadata2.add('key5', 'value5a');
metadata2.add('key5', 'value5b');
const metadata2IR = metadata2.getInternalRepresentation();
metadata.merge(metadata2);
// Ensure metadata2 didn't change
assert.deepStrictEqual(
metadata2.getInternalRepresentation(),
metadata2IR
);
assert.deepStrictEqual(metadata.get('key1'), ['value1', 'value1']);
assert.deepStrictEqual(metadata.get('key2'), ['value2a', 'value2b']);
assert.deepStrictEqual(metadata.get('key3'), ['value3a', 'value3b']);
assert.deepStrictEqual(metadata.get('key4'), ['value4']);
assert.deepStrictEqual(metadata.get('key5'), ['value5a', 'value5b']);
});
});
describe('toHttp2Headers', () => {
it('creates an OutgoingHttpHeaders object with expected values', () => {
metadata.add('key1', 'value1');
metadata.add('Key2', 'value2');
metadata.add('KEY3', 'value3a');
metadata.add('key3', 'value3b');
metadata.add('key-bin', Buffer.from(range(0, 16)));
metadata.add('key-bin', Buffer.from(range(16, 32)));
metadata.add('key-bin', Buffer.from(range(0, 32)));
const headers = metadata.toHttp2Headers();
assert.deepStrictEqual(headers, {
key1: ['value1'],
key2: ['value2'],
key3: ['value3a', 'value3b'],
'key-bin': [
'AAECAwQFBgcICQoLDA0ODw==',
'EBESExQVFhcYGRobHB0eHw==',
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=',
],
});
});
it('creates an empty header object from empty Metadata', () => {
assert.deepStrictEqual(metadata.toHttp2Headers(), {});
});
});
describe('fromHttp2Headers', () => {
it('creates a Metadata object with expected values', () => {
const headers = {
key1: 'value1',
key2: ['value2'],
key3: ['value3a', 'value3b'],
key4: ['part1, part2'],
'key-bin': [
'AAECAwQFBgcICQoLDA0ODw==',
'EBESExQVFhcYGRobHB0eHw==',
'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=',
],
};
const metadataFromHeaders = TestMetadata.fromHttp2Headers(headers);
const internalRepr = metadataFromHeaders.getInternalRepresentation();
const expected: MetadataObject = new Map<string, MetadataValue[]>([
['key1', ['value1']],
['key2', ['value2']],
['key3', ['value3a', 'value3b']],
['key4', ['part1, part2']],
[
'key-bin',
[
Buffer.from(range(0, 16)),
Buffer.from(range(16, 32)),
Buffer.from(range(0, 32)),
],
],
]);
assert.deepStrictEqual(internalRepr, expected);
});
it('creates an empty Metadata object from empty headers', () => {
const metadataFromHeaders = TestMetadata.fromHttp2Headers({});
const internalRepr = metadataFromHeaders.getInternalRepresentation();
assert.deepStrictEqual(internalRepr, new Map<string, MetadataValue[]>());
});
});
});