blob: 7866b177a69e682aba2104433ce2f77ce10dfa97 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
var test = require('prova');
var vanadium = require('../../');
var serve = require('./serve');
var leafDispatcher = require('../../src/rpc/leaf-dispatcher');
var Deferred = require('../../src/lib/deferred');
var builtins = require('../../src/vdl/builtins');
var stringify = require('../../src/vdl/stringify');
var TypeUtil = require('../../src/vdl/type-util');
var typeServiceVdl =
require('../vdl-out/javascript-test/services/type-service');
var typedStreamingServiceVdl =
require('../vdl-out/javascript-test/services/typed-streaming-service');
var vdl = vanadium.vdl;
// TODO(bprosnitz) Combine CacheService and CacheServicePromises so there
// isn't as much duplicated code.
var CacheService = {
cacheMap: {},
set: function(context, serverCall, key, value, cb) {
this.cacheMap[key] = value;
process.nextTick(function() {
cb(null, undefined);
});
},
get: function(context, serverCall, key, cb) {
var val = this.cacheMap[key];
if (val === undefined) {
var message = 'unknown key ' + JSON.stringify(key);
var err = new Error(message);
process.nextTick(function() {
cb(err);
});
} else {
process.nextTick(function() {
cb(undefined, val);
});
}
} ,
// TODO(bprosnitz) Also test streaming with no return arg.
multiGet: function(context, serverCall, $stream, cb) {
var numReceived = 0;
$stream.on('end', function close() {
cb(null, numReceived);
});
$stream.on('error', function error(e) {
cb(e);
});
var self = this;
$stream.on('data', function(key) {
numReceived++;
if (key !== null) {
var val = self.cacheMap[key];
if (val === undefined) {
cb(new Error('unknown key'));
}
$stream.write(val);
}
});
$stream.read();
},
doNothingStream: function(ctx, serverCall, $stream, cb) {
cb(null, undefined);
},
nonAsyncFunction: function(ctx, serverCall, cb) {
cb(null, 'RESULT');
}
};
var CacheServicePromises = {
cacheMap: {},
set: function(context, serverCall, key, value) {
this.cacheMap[key] = value;
},
get: function(context, serverCall, key) {
var def = new Deferred();
var val = this.cacheMap[key];
process.nextTick(function() {
if (val === undefined) {
// Since we're rejecting the promise before we've returned it
// we'll register a catch handler now to avoid an unhandled rejection
// warning.
def.promise.catch(function() {});
def.reject('unknown key');
} else {
def.resolve(val);
}
});
return def.promise;
} ,
multiGet: function(context, serverCall, $stream) {
var numReceived = 0;
var def = new Deferred();
$stream.on('end', function() {
def.resolve(numReceived);
});
$stream.on('error', function(e) {
def.reject(e);
});
var self = this;
$stream.on('data', function(key) {
numReceived++;
if (key !== null) {
var val = self.cacheMap[key];
if (val === undefined) {
def.reject('unknown key');
}
$stream.write(val);
}
});
$stream.read();
return def.promise;
},
doNothingStream: function(ctx, serverCall, $stream) {
},
nonAsyncFunction: function(ctx, serverCall) {
return 'RESULT';
}
};
runCache({
testName: 'without VDL (JSValue) using callbacks',
definition: CacheService,
name: 'foo.Cache'
});
runCache({
testName: 'without VDL (JSValue) using promises',
definition: CacheServicePromises,
name: 'foo.Cache'
});
// options: testName, definition, name
function runCache(options) {
var namePrefix = 'Test JS client/server rpc ' + options.testName + ' - ';
test(namePrefix + 'cache.set(key, string) -> cache.get(key)',
function(t) {
setup(options, function(err, ctx, cache, end) {
t.error(err, 'should not error on setup');
cache.set(ctx, 'foo', 'bar', function(err, res) {
t.error(err, 'should not error on set(...)');
t.notOk(res, 'should be null');
cache.get(ctx, 'foo', function(err, res) {
t.error(err, 'should not error on get(...)');
t.equal(res, 'bar');
end(t);
});
});
});
});
test(namePrefix + 'cache.set(key, object, callback)', function(t) {
setup(options, function(err, ctx, cache, end) {
t.error(err, 'should not error on setup');
// Expect a map as the JSValue.
var expected = new Map([['a', 'foo'], ['b', 2]]);
cache.set(ctx, 'myObject', expected, function(err, res) {
t.error(err, 'should not error on set(...)');
t.equal(res, null, 'should be null');
cache.get(ctx, 'myObject', function(err, res) {
t.error(err, 'should not error on get(...)');
t.deepEqual(res, expected, 'should match object');
end(t);
});
});
});
});
test(namePrefix + 'cache.get("bad-key", callback) - failure',
function(t) {
setup(options, function(err, ctx, cache, end) {
t.error(err, 'should not error on setup');
cache.get(ctx, 'bad-key', function(err, res) {
t.ok(err, 'should err on get(...)');
// TODO(bjornick): Use the constant generated by the vdl generator.
t.equal(err.id, 'v.io/v23/verror.Unknown');
end(t);
});
});
});
test(namePrefix + 'cache.badMethod() - failure', function(t) {
setup(options, function(err, ctx, cache, end) {
t.error(err, 'should not error on setup');
t.throws(function() {
cache.badMethod();
});
end(t);
});
});
test(namePrefix + 'cache.multiGet()', function(t) {
// `cache.multiGet()` returns an object that has a "stream" attribute.
// The way the streaming interface is implmented for cache.multiGet()
// is that you use stream.write(key) to get the value of a key. The value
// is emitted on the stream's data event. In this test there are a few
// steps to set this up:
//
// 1. Prime the cache by setting a bunch of key/values
// 2. Add a listener or create a stream reader to receive the values
// 3. Assert the values are correct
// 4. End the stream.
setup(options, function(err, ctx, cache, end){
// 1. Prime the cache by setting a bunch of key/values
// Build a map of items
var items = {};
var numItems = 3;
for (var i = 0; i < numItems; ++i) {
items[i] = {
key: i,
value: 'value: ' + i
};
}
// Add them to the cache
var jobs = Object.keys(items).map(function(key) {
return cache.set(ctx, key, JSON.stringify(items[key]));
});
Promise
.all(jobs)
.then(function() {
// 2. Add a listener or create a stream reader to receive the values
var promise = cache.multiGet(ctx);
var stream = promise.stream;
var writes = 0;
var reads = 0;
// Error handling boilerplate
promise.then(function(numReceived) {
t.equal(numReceived, numItems, 'received correct number of items');
t.equal(reads, numItems, 'had correct number of reads');
t.equal(writes, numItems, 'has correct number of writes');
end(t);
}).catch(error);
stream.on('error', error);
// 3. Assert the values are correct
// stream "data" event emits cached values
stream.on('data', function(value) {
var string = value.toString();
var json = JSON.parse(string);
var actual = json.value;
var expected = items[json.key].value;
t.equal(actual, expected);
reads++;
});
Object.keys(items).forEach(function(key) {
stream.write(key);
writes++;
});
// 4. End the stream.
stream.end();
});
function error(err) {
t.error(err, 'should not error');
end(t);
}
});
});
function setup(options, cb) {
var dispatcher = leafDispatcher(options.definition);
serve('testing/cache', dispatcher, function(err, res) {
cb(err, res.runtime.getContext(), res.service, res.end);
});
}
}
function TypeService() {}
TypeService.prototype = new typeServiceVdl.TypeService();
TypeService.prototype.isTyped = function(context, serverCall, any) {
// We expect to receive the internally typed value of the any.
// However, clients who send JSValue will not produce a typed value here.
return TypeUtil.isTyped(any);
};
TypeService.prototype.isString = function(context, serverCall, str) {
// We expect to receive a native string, if the client sent us one.
return (typeof str === 'string');
};
TypeService.prototype.isStruct = function(context, serverCall, struct) {
// A struct should always be typed.
if (TypeUtil.isTyped(struct)) {
return;
}
// If it was untyped (a JSValue object), then the code is incorrect.
throw new Error('did not receive a typed struct' + stringify(struct));
};
TypeService.prototype.swap = function(context, serverCall, a, b) {
return [b, a];
};
runTypeService({
testName: 'typed, non-async',
definition: new TypeService(),
name: 'foo.TypeService'
});
// options: testName, definition, name
function runTypeService(options) {
var namePrefix = 'Test JS client/server rpc ' + options.testName + ' - ';
// This test ensures that typed values are sent between JS server and client.
// The server expects an input of the ANY type, which means that it ought to
// receive a typed value, if we send a typed value.
// If we send a JSValue, then it will not end up being wrapped.
test(namePrefix + 'typeService.isTyped(...)', function(t) {
setup(options, function(err, ctx, typeService, end) {
t.error(err, 'should not error on setup');
typeService.isTyped(ctx, 'foo', function(err, res) {
t.error(err, 'should not error on isTyped(...)');
// Use equal instead of notOk to ensure that res is not wrapped.
t.equal(res, false, '\'foo\' is an untyped string');
var VomStr = vdl.registry.lookupOrCreateConstructor(vdl.types.STRING);
var typedString = new VomStr('food');
typeService.isTyped(ctx, typedString, function(err, res) {
t.error(err, 'should not error on isTyped(...)');
// Use equal instead of ok to ensure that res is not wrapped.
t.equal(res, true, 'VomStr(\'food\') is a typed string');
end(t);
});
});
});
});
test(namePrefix + 'typeService.isTyped(...) - promise', function(t) {
setup(options, function(err, ctx, typeService, end) {
t.error(err, 'should not error on setup');
typeService.isTyped(ctx, 'glue').then(function(res) {
t.error(err, 'should not error on isTyped(...)');
// Use equal instead of notOk to ensure that res is not wrapped.
t.equal(res, false, '\'glue\' is an untyped string');
var VomStr = vdl.registry.lookupOrCreateConstructor(vdl.types.STRING);
var typedString = new VomStr('glued');
typeService.isTyped(ctx, typedString, function(err, res) {
t.error(err, 'should not error on isTyped(...)');
// Use equal instead of ok to ensure that res is not wrapped.
t.equal(res, true, 'VomStr(\'glued\') is a typed string');
end(t);
});
}).catch(function error(err) {
t.error(err, 'should not error');
end(t);
});
});
});
test(namePrefix + 'typeService.isTyped(undefined) - promise', function(t) {
setup(options, function(err, ctx, typeService, end) {
t.error(err, 'should not error on setup');
// Check that you can leave an optional (or any) inArg as undefined.
// It should not be mistaken for a callback.
typeService.isTyped(ctx, undefined).then(function(res) {
t.error(err, 'should not error on isTyped(undefined)');
t.equal(res, false, 'undefined is treated like JSValue null');
// Lenient: Having both arg and cb as undefined is fine too.
typeService.isTyped(ctx, undefined, undefined).then(function(res) {
t.error(err, 'should not error on isTyped(undefined, undefined)');
t.equal(res, false, 'undefined is treated like JSValue null');
// Having any more undefined's is an error (too many args).
typeService.isTyped(ctx, undefined, undefined, undefined).then(
function(res) {
t.fail(err, 'should have errored');
end(t);
}).catch(function(err) {
t.ok(err, 'should error');
end(t);
});
});
}).catch(function error(err) {
t.error(err, 'should not error');
end(t);
});
});
});
// This test ensures that typed values sent between JS server and client are
// unwrapped when being processed. Further, the client disallows sending the
// wrong type to the server.
test(namePrefix + 'typeService.isString(str)', function(t) {
setup(options, function(err, ctx, typeService, end) {
t.error(err, 'should not error on setup');
typeService.isString(ctx, 'foo', function(err, res) {
t.error(err, 'should not error on isString(<a string>)');
// Use equal instead of ok to ensure that res is not wrapped.
t.equal(res, true, '\'foo\' is a string');
typeService.isString(ctx, 0, function(err, res) {
t.ok(err, 'should error on isString(<not a string>)');
end(t);
});
});
});
});
// This test ensures that a typed struct has its type on the other side.
// That would prove that it was not decoded as a JSValue.
test(namePrefix + 'typeService.isStruct(struct)', function(t) {
setup(options, function(err, ctx, typeService, end) {
t.error(err, 'should not error on setup');
typeService.isStruct(ctx, {}, function(err, res) {
t.error(err, 'should not error on isStruct(...)');
end(t);
});
});
});
// This test ensures that multiple typed I/O arguments are possible in JS.
test(namePrefix + 'typeService.swap(a, b)', function(t) {
setup(options, function(err, ctx, typeService, end) {
t.error(err, 'should not error on setup');
// Start by swapping JSValue. There are no types attached when returned.
var a = '33';
var b = 33;
typeService.swap(ctx, a, b, function(err, res1, res2) {
t.error(err, 'should not error on swap(...)');
t.deepEqual([res1, res2], [b, a], 'correctly swapped the 2 inputs');
// Now, swap a typed value (aa) with a wrapped and typed value (bb).
var simpleType = {
name: 'SimpleStruct',
kind: vdl.kind.STRUCT,
fields: [
{
name: 'Foo',
type: vdl.types.INT32
},
{
name: 'Bar',
type: vdl.types.BOOL
}
]
};
var SimpleStruct = vdl.registry.lookupOrCreateConstructor(simpleType);
var aa = new SimpleStruct({
foo: 10,
bar: true
});
var simpleTypeB = vdl.types.INT32;
var SimpleInt32 = vdl.registry.lookupOrCreateConstructor(simpleTypeB);
var bb = new SimpleInt32(-32);
typeService.swap(ctx, aa, bb, function(err, res1, res2) {
t.error(err, 'should not error on swap(...)');
t.deepEqual([res1, res2], [bb, aa], 'correctly swapped the 2 inputs');
// Verify that res2 (the original aa) still has the right type.
t.ok(TypeUtil.isTyped(res2), 'aa is still typed');
t.deepEqual(res2._type, simpleType, 'aa has the correct type');
// Verify that res1 (the original bb) still has the right type.
t.ok(TypeUtil.isTyped(res1), 'bb is still typed');
t.deepEqual(res1._type, simpleTypeB, 'bb has the correct type');
end(t);
});
});
});
});
function setup(options, cb) {
var dispatcher = leafDispatcher(options.definition);
serve('testing/typeService', dispatcher, function(err, res) {
cb(err, res.runtime.getContext(), res.service, res.end);
});
}
}
var boolListType = typedStreamingServiceVdl.BoolList.prototype._type;
var numStructType = typedStreamingServiceVdl.NumStruct.prototype._type;
var typeListType = typedStreamingServiceVdl.TypeList.prototype._type;
// TODO(alexfandrianto): Add a callback version of the typed streaming service.
// See each test case for what the service method tests.
function TypedStreamingService() {}
TypedStreamingService.prototype =
new typedStreamingServiceVdl.TypedStreamingService();
// inStreamOnly verifies that typed inStreams work properly.
TypedStreamingService.prototype.inStreamOnly =
function(ctx, serverCall, numTimes, $stream) {
// Receive stream values numTimes
var numReceived = 0;
var def = new Deferred();
$stream.on('end', function() {
if (numReceived !== numTimes) {
var err = new Error('Got ' + numReceived + '. Wanted ' + numTimes);
def.reject(err);
}
def.resolve(numReceived);
});
$stream.on('error', function(e) {
def.reject(e);
});
$stream.on('data', function(str) {
if (typeof str !== 'string') {
def.reject(new Error('Expected a string, but got ' + str));
}
numReceived++;
});
$stream.read();
$stream.write('No outstream type; this cannot be sent');
return def.promise;
};
// outStreamOnly verifies that typed outStreams work properly.
TypedStreamingService.prototype.outStreamOnly =
function(ctx, serverCall, numTimes, $stream, cb) {
// Send stream values numTimes
var numSent = 0;
while (numSent < numTimes) {
$stream.write(numSent); // Despite sending int, we autoconvert to BigInt.
numSent++;
}
return cb(null, numSent);
};
// bidirBoolListNegationsStream tests that bidirectional streams can send
// composite types back and forth, as well as modify the data items streamed.
TypedStreamingService.prototype.bidirBoolListNegationStream =
function(ctx, serverCall, $stream) {
// Given a list of bool, send the opposite bools back.
var numReceived = 0;
var def = new Deferred();
$stream.on('end', function() {
def.resolve(numReceived);
});
$stream.on('error', function(e) {
def.reject(e);
});
$stream.on('data', function(boolList) {
numReceived++;
var oppList = boolList.map(function(b) {
return !b;
});
$stream.write(oppList);
});
$stream.read();
return def.promise;
};
// structValueStream converts a number to a struct based on that number.
// Ensures that custom-defined types can be sent across the stream.
TypedStreamingService.prototype.structValueStream =
function(ctx, serverCall, $stream) {
// Given a number, send a number struct back.
var numReceived = 0;
var def = new Deferred();
$stream.on('end', function() {
def.resolve(numReceived);
});
$stream.on('error', function(e) {
def.reject(e);
});
$stream.on('data', function(num) {
numReceived++;
$stream.write({
'number': num,
'bigInt': vdl.BigInt.fromNativeNumber(num),
'string': '' + num
});
});
$stream.read();
return def.promise;
};
// anyStream tests that typed values can pass through a bidirectional stream.
TypedStreamingService.prototype.anyStream =
function(ctx, serverCall, types, $stream) {
// Given a list of types, listen to a stream of values.
// Errors if any of the values received did not match their expected type.
// Stream those values back directly.
var typesReceived = [];
var def = new Deferred();
$stream.on('end', function() {
def.resolve(typesReceived);
});
$stream.on('error', function(e) {
def.reject(e);
});
$stream.on('data', function(val) {
// Verify that the value has no type if native, or matches, otherwise.
var expectedType = types[typesReceived.length];
if (expectedType.equals(vdl.types.JSVALUE) && val._type !== undefined) {
def.reject(new Error('Native value had a type: ' +
JSON.stringify(val)));
}
if (!expectedType.equals(vdl.types.JSVALUE) &&
!expectedType.equals(val._type)) {
def.reject(new Error('Value had wrong type: ' +
JSON.stringify(val)));
}
// The value had the corerct type. Write the same value back.
// Note: Native values lack types, so use the JSValue type instead.
typesReceived.push(val._type || vdl.types.JSVALUE);
$stream.write(val);
});
$stream.read();
return def.promise;
};
runTypedStreamingService({
testName: 'typed, streaming, non-async',
definition: new TypedStreamingService(),
name: 'foo.TypedStreamingService'
});
// options: testName, definition, name
function runTypedStreamingService(options) {
var namePrefix = 'Test JS client/server rpc ' + options.testName + ' - ';
// typedStreamingService.inStreamOnly tests:
// - correct # of values sent to server
// - values received by server have correct type
// - outStream is null
// - client never gets data, even though server tries to send on outStream
test(namePrefix + 'typedStreamingService.inStreamOnly(...)',
function(t) {
setup(options, function(err, ctx, typedStreamingService, end) {
t.error(err, 'should not error on setup');
// The # of strings we intend to send.
var strList = ['asdf', ';lkj', 'qwer', 'poiu'];
var numStrs = strList.length;
// Prepare and run the stream test.
var testdata = {
inArg: numStrs,
inData: strList,
serviceMethod: typedStreamingService.inStreamOnly,
writeType: vdl.types.STRING,
readType: null,
onResolveFunc: function(numReceived) {
t.equal(numReceived, numStrs,
'service received correct # of strings');
end(t);
},
onDataFunc: function(value, dataIndex) {
t.fail('received data from the stream: ' + JSON.stringify(value));
}
};
streamTest(t, ctx, testdata, end);
});
});
// This test verifies that the stream promise rejects normally.
// We send 4 items, but we claim to the server that we will send 6.
// Note: the stream also passes the same reject error to the error stream.
test(namePrefix + 'typedStreamingService.inStreamOnly(...) - failure',
function(t) {
setup(options, function(err, ctx, typedStreamingService, end) {
t.error(err, 'should not error on setup');
// The # of strings we intend to send.
var strList = ['asdf', ';lkj', 'qwer', 'poiu'];
var numStrs = strList.length + 2; // mismatch
// The stream will send an error on the error stream just before the
// Promise rejects.
var onErrorFunc = function(err) {
t.ok(err, 'should error');
t.ok(err.message.indexOf('Got 4. Wanted 6') !== -1,
'has correct error message');
};
// Prepare and run the stream test.
var testdata = {
inArg: numStrs,
inData: strList,
serviceMethod: typedStreamingService.inStreamOnly,
writeType: vdl.types.STRING,
readType: null,
onResolveFunc: function(numReceived) {
t.fail('should have errored; did not send correct # of strings');
end(t);
},
onDataFunc: function(value, dataIndex) {
t.fail('received data from the stream: ' + JSON.stringify(value));
},
onErrorFunc: onErrorFunc,
onRejectFunc: function(err) {
onErrorFunc(err);
end(t);
}
};
streamTest(t, ctx, testdata, end);
});
});
// This test verifies that the client cannot send a bad type onto the stream.
// It also verifies that this bad value is not sent to the server.
// Thus, the promise rejects, and the client gets an error while attempting to
// write the bad value (write an int instead of a string).
// Note: the stream also passes the same reject error to the error stream.
test(namePrefix + 'typedStreamingService.inStreamOnly(...) - failure 2',
function(t) {
setup(options, function(err, ctx, typedStreamingService, end) {
t.error(err, 'should not error on setup');
// The # of strings we intend to send.
var strList = [6];
var numStrs = strList.length;
// The stream will send an error on the error stream just before the
// Promise rejects.
var onErrorFunc = function(err) {
t.ok(err, 'should error');
t.ok(err.message.indexOf('Got 0. Wanted 1') !== -1,
'has correct error message');
};
// Prepare and run the stream test.
var testdata = {
inArg: numStrs,
inData: strList,
serviceMethod: typedStreamingService.inStreamOnly,
writeType: vdl.types.STRING,
readType: null,
onResolveFunc: function(numReceived) {
t.fail('should have errored; sent an int');
end(t);
},
onDataFunc: function(value, dataIndex) {
t.fail('received data from the stream: ' + JSON.stringify(value));
},
inDataThrowRegexp: /.*cannot convert to string.*/,
onErrorFunc: onErrorFunc,
onRejectFunc: function(err) {
onErrorFunc(err);
end(t);
}
};
streamTest(t, ctx, testdata, end);
});
});
// typedStreamingService.outStreamOnly tests:
// - correct # of values read and outputted
// - values received by server have the correct type
// - inStream is null
test(namePrefix + 'typedStreamingService.outStreamOnly(...)', function(t) {
setup(options, function(err, ctx, typedStreamingService, end) {
t.error(err, 'should not error on setup');
// The # of BigInts we want to receive and # received so far.
var numInts = 3;
var numOutStream = 0;
// Prepare and run the stream test.
var testdata = {
inArg: numInts,
inData: [],
serviceMethod: typedStreamingService.outStreamOnly,
writeType: null,
readType: vdl.types.INT64,
onResolveFunc: function(numSent) {
t.equal(numSent, numInts, 'service knows # of values sent');
t.equal(numOutStream, numInts, 'service sent correct # of values');
end(t);
},
onDataFunc: function(value, dataIndex) {
t.ok(value instanceof vdl.BigInt, 'value is a BigInt');
numOutStream++;
}
};
streamTest(t, ctx, testdata, end);
});
});
// typedStreamingService.bidirBoolListNegationStream tests:
// - custom defined type (simple) can be sent and received properly
// - the values can be modified and returned (each bool is negated)
test(namePrefix + 'typedStreamingService.bidirBoolListNegationStream()',
function(t) {
setup(options, function(err, ctx, typedStreamingService, end){
t.error(err, 'should not error on setup');
// These are the testcases.
var boolLists = [
undefined, // Note: undefined autoconverts to empty list.
[],
[true],
[false, true, true],
[undefined, false], // Note: undefined autoconverts to false.
];
var expectedLists = [
[],
[],
[false],
[true, false, false],
[true, true]
];
// Prepare and run the stream test.
var testdata = {
inArg: undefined,
inData: boolLists,
serviceMethod: typedStreamingService.bidirBoolListNegationStream,
writeType: boolListType,
readType: boolListType,
onResolveFunc: function(numReceived) {
t.deepEqual(numReceived, boolLists.length,
'service sent correct # of values');
end(t);
},
onDataFunc: function(actual, dataIndex) {
t.ok(Array.isArray(actual), 'value is an array');
t.deepEqual(actual, expectedLists[dataIndex],
'bools were flipped');
}
};
streamTest(t, ctx, testdata, end);
});
});
// typedStreamingService.structValueStream tests:
// - values with a named struct type can be received properly
test(namePrefix + 'typedStreamingService.structValueStream()', function(t) {
setup(options, function(err, ctx, typedStreamingService, end){
t.error(err, 'should not error on setup');
// These are the testcases.
var numbers = [
undefined, // Note: undefined autoconverts to 0
3,
-500000
];
var NumStruct = vdl.registry.lookupOrCreateConstructor(numStructType);
var expectedNumStructs = [
new NumStruct({
string: '0'
}),
new NumStruct({
number: 3,
bigInt: vdl.BigInt.fromNativeNumber(3),
string: '3'
}),
new NumStruct({
number: -500000,
bigInt: vdl.BigInt.fromNativeNumber(-500000),
string: '-500000'
})
];
// Prepare and run the stream test.
var testdata = {
inArg: undefined,
inData: numbers,
serviceMethod: typedStreamingService.structValueStream,
writeType: vdl.types.FLOAT64,
readType: numStructType,
onResolveFunc: function(numReceived) {
t.deepEqual(numReceived, numbers.length,
'service sent correct # of values');
end(t);
},
onDataFunc: function(actual, dataIndex) {
t.ok(actual instanceof NumStruct, 'value is a NumStruct');
t.deepEqual(actual, expectedNumStructs[dataIndex],
'number converted to NumStruct');
}
};
streamTest(t, ctx, testdata, end);
});
});
// typedStreamingService.anyStream tests:
// - the any stream succeeds in both directions
// - complicated types can be sent across the stream in both directions
// - the types received by the server match the expected types
// - the types received by the client match the expected types
test(namePrefix + 'typedStreamingService.anyStream()', function(t) {
setup(options, function(err, ctx, typedStreamingService, end){
t.error(err, 'should not error on setup');
// These are the testcases.
var typesSent = [
vdl.types.JSVALUE,
vdl.types.INT32,
vdl.types.INT64,
vdl.types.COMPLEX128,
vdl.types.STRING,
vdl.types.BOOL,
numStructType,
boolListType,
typeListType
];
var NumStruct = vdl.registry.lookupOrCreateConstructor(numStructType);
var BoolList = vdl.registry.lookupOrCreateConstructor(boolListType);
var TypeList = vdl.registry.lookupOrCreateConstructor(typeListType);
var sendList = [
3.14,
new builtins.INT32(5),
new builtins.INT64(-15),
new builtins.COMPLEX128(new vdl.Complex(5, -5)),
new builtins.STRING('abc'),
new builtins.BOOL(true),
new NumStruct({
number: -0.5
}),
new BoolList([true, true, false, false]),
new TypeList(typesSent)
];
// Prepare and run the stream test.
var testdata = {
inArg: typesSent,
inData: sendList,
serviceMethod: typedStreamingService.anyStream,
writeType: vdl.types.ANY,
readType: vdl.types.ANY,
onResolveFunc: function(typesReceived) {
t.deepEqual(typesReceived, typesSent,
'service sent back the correct types');
end(t);
},
onDataFunc: function(actual, dataIndex) {
if (actual._type === undefined) {
t.ok(typesSent[dataIndex].equals(vdl.types.JSVALUE),
'value is native');
} else {
t.deepEqual(actual._type, typesSent[dataIndex], 'type matches');
}
t.deepEqual(actual, sendList[dataIndex], 'received correct value');
}
};
streamTest(t, ctx, testdata, end);
});
});
function setup(options, cb) {
var dispatcher = leafDispatcher(options.definition);
serve('testing/typeService', dispatcher, function(err, res) {
cb(err, res.runtime.getContext(), res.service, res.end);
});
}
}
/*
* Performs a stream test that assumes <= 1 input arg to the service method.
* testdata contains inArg, inData, serviceMethod, writeType, readType,
* onResolveFunc, onDataFunc
* Note: onResolveFunc and onDataFunc should end the test.
* Optional testdata fields: inDataThrowRegexp, onErrorFunc, and onRejectFunc;
* these are most useful for error test cases.
*/
function streamTest(t, ctx, testdata, end) {
// The Error function is optional; successful test cases never need it.
function error(err) {
t.error(err, 'should not error');
end(t);
}
// Determine the correct onRejectFunc and onErrorFunc handlers.
var onRejectFunc = testdata.onRejectFunc || error;
var onErrorFunc = testdata.onErrorFunc || error;
// 1. Create a stream reader/writer to receive the values
var promise = testdata.serviceMethod(ctx, testdata.inArg);
var stream = promise.stream;
t.deepEqual(stream.writeType, testdata.writeType, 'inStream matches type');
t.deepEqual(stream.readType, testdata.readType, 'outStream matches type');
// 2. Handle RPC
promise.then(testdata.onResolveFunc).catch(onRejectFunc);
// 3. Setup listeners for the stream. Data should be NumStructs
stream.on('error', onErrorFunc);
var dataIndex = 0;
stream.on('data', function(actual) {
testdata.onDataFunc(actual, dataIndex);
dataIndex++;
});
// 4. Send data through the stream.
for (var i = 0; i < testdata.inData.length; i++) {
if (testdata.inDataThrowRegexp) {
t.throws(stream.write.bind(stream, testdata.inData[i]),
testdata.inDataThrowRegexp, 'stream write throws on bad input');
} else {
stream.write(testdata.inData[i]);
}
}
// 5. End the stream.
stream.end();
}
test('Test server stream exception handling', function(assert) {
var exception;
if (typeof window === 'undefined') {
process.on('uncaughtException', function setexception(err) {
exception = err;
process.removeListener('uncaughtException', setexception);
});
} else {
window.onerror = function(message, url, line, column, err) {
exception = err;
// Put it back to the default
window.onerror = null;
};
}
var service = {
get: function get(context, serverCall, $stream, cb) {
$stream.on('end', cb.bind(null, null, {}));
$stream.on('error', cb);
$stream.on('data', function ondata(buffer) {
throw new Error('Woah!');
});
}
};
setup(service, function ready(err, context, remote, end) {
assert.error(err, 'should not error on setup');
var stream = remote.get(context, function(err) {
assert.error(err);
assert.ok(exception, 'stream exceptions should be raised');
end(assert);
}).stream;
stream.write('foo');
stream.end();
});
function setup(service, cb) {
var dispatcher = leafDispatcher(service);
var name = 'test/server-stream-exception';
serve(name, dispatcher, function(err, res) {
cb(err, res.runtime.getContext(), res.service, res.end);
});
}
});