I'm experimenting with Linq2IndexedDB (v. 1.0.21) via unit tests (via Mocha), but I can't even make a simple insert work. What happens (when running under Google Chrome) is an internal exception is thrown on line 1535 of Linq2IndexedDB.js:
Uncaught TypeError: Cannot read property 'version' of undefined
My unit test code looks as follows; there's basically one test, "it can add objects":
"use strict";
define(["db", "linq2indexeddb", "chai", "underscore", "stacktrace"], function (db, linq2indexeddb, chai, _,
printStacktrace) {
var should = chai.should();
describe("db", function () {
var _db;
function fail(done, reason, err) {
if (typeof reason === "string") {
reason = new Error(reason);
}
if (!reason) {
console.log(typeof done, typeof reason);
reason = new Error("There's been an error, but no reason was supplied!");
var st = printStacktrace({e: reason});
console.log(st);
}
if (typeof done !== "function") {
throw new Error("Was not supplied a function for 'done'!");
}
done(reason);
}
// Bind done as the first argument to the fail function
function bindFail(done, reason) {
if (typeof done !== "function") {
throw new Error("done must be a function");
}
return _.partial(fail, done, reason);
}
beforeEach(function (done) {
_db = linq2indexeddb("test", null, true);
_db.deleteDatabase()
.done(function () {
_db.initialize()
.done(done)
.fail(bindFail(done, "Initializing database failed"));
})
.fail(bindFail(done, "Deleting database failed"));
});
it("can add objects", function (done) {
console.log("Starting test");
var refObj = {"key": "value"};
_db.linq.from("store").insert(refObj, "Key")
.done(function () {
console.log("Added object successfully");
done();
})
.fail(bindFail(done, "Inserting object failed"));
});
});
});
Am I doing something wrong here, or is there a bug in Linq2IndexedDB (or both)?
I've put up a corresponding test project on Github, complete with a Karma configuration, so you can run the included tests easily. The Karma configuration assumes you have Chrome installed.
I found a couple of issues:
I got the Linq2IndexedDB worker location wrong, it should be: '/base/lib/Linq2IndexedDB.js'
Inserting with the object with an out-of-line key doesn't work.
I eventually got insertion working on IE 10 and Chrome, although I'm still struggling with PhantomJS. To get it working under Chrome, I had to specify my schema explicitly, I suspect this is due to a bug in Linq2IndexedDB. My working solution is as follows:
Test:
"use strict";
define(["db", "linq2indexeddb", "chai", "underscore", "stacktrace"], function (db, linq2indexeddb, chai, _,
printStacktrace) {
var should = chai.should();
describe("db", function () {
var _db;
function fail(done, reason, err) {
console.log("err:", err);
if (typeof reason === "string") {
reason = new Error(reason);
}
if (!reason) {
console.log(typeof done, typeof reason);
reason = new Error("There's been an error, but no reason was supplied!");
var st = printStacktrace({e: reason});
console.log(st);
}
if (typeof done !== "function") {
throw new Error("Was not supplied a function for 'done'!");
}
done(reason);
}
// Bind done as the first argument to the fail function
function bindFail(done, reason) {
if (typeof done !== "function") {
throw new Error("done must be a function");
}
return _.partial(fail, done, reason);
}
beforeEach(function (done) {
// Linq2IndexedDB's web worker needs this URL
linq2indexeddb.prototype.utilities.linq2indexedDBWorkerFileLocation = '/base/lib/Linq2IndexedDB.js'
_db = new db.Database("test");
console.log("Deleting database");
_db.deleteDatabase()
.done(function () {
console.log("Initializing database");
_db.initialize()
.done(done)
.fail(bindFail(done, "Initializing database failed"));
})
.fail(bindFail(done, "Deleting database failed"));
});
it("can add objects", function (done) {
console.log("Starting test");
var refObj = {"key": "value"};
_db.insert(refObj)
.done(function () {
done();
})
.fail(bindFail(done, "Database insertion failed"));
});
});
});
Implementation:
define("db", ["linq2indexeddb"], function (linq2indexeddb) {
function getDatabaseConfiguration() {
var dbConfig = {
version: 1
};
// NOTE: definition is an array of schemas, keyed by version;
// this allows linq2indexedDb to do auto-schema-migrations, based upon the current dbConfig.version
dbConfig.definition = [{
version: 1,
objectStores: [
{ name: "store", objectStoreOptions: { keyPath: 'key' } },
],
defaultData: []
},
];
return dbConfig;
}
var module = {
Database: function (name) {
var self = this;
self._db = linq2indexeddb(name, getDatabaseConfiguration());
self.deleteDatabase = function () {
return self._db.deleteDatabase();
};
self.initialize = function () {
return self._db.initialize();
};
self.insert = function (data) {
return self._db.linq.from("store").insert(data);
};
}
};
return module;
});
EDIT:
The reason it wasn't working under PhantomJS was that I'd enabled debug logging in Linq2IndexedDB, for some reason this would seemingly clog up Karma's pipes at some point. After turning off debug logging, all configurations work.
Related
Module under test:
'use strict';
const config = require('config');
const q = require('q');
class RedisAccess {
static getValue(key) {
let deferred = q.defer();
if (config.redis.disableInteraction) {
deferred.resolve();
return deferred.promise;
}
config.redisClient.get(key, function handleResults(err, result) {
...
return deferred.promise;
}
}
exports = module.exports = RedisAccess;
Test:
var proxyquire = require('proxyquire').noPreserveCache();
var assert = require('assert');
var readdirError = new Error('some error');
var redisClientStub = { };
var calledBack;
// Override redisClient used by RedisAccess.js.
var redisClientProxy = proxyquire('../../../lib/data/redis/RedisAccess.js', { 'config' : redisClientStub });
// Test redisClient.get(...) to retrieve value given key using proxyquire for redisClient.
redisClientStub.redisClient.get = function (key, cb) {
cb(null, 'hello world');
};
calledBack = false;
// Test redisClient getValue async function.
redisClientProxy.getValue('some_key', function (err, value) {
assert.equal(err, null);
assert.equal('value', 'hello world');
callback = true;
});
The error when I execute the test is:
redisClientStub.redisClient.get = function (key, cb) {
^
TypeError: Cannot set property 'get' of undefined
How do I properly stub the config.redisClient.get(...) function?
I figured this out. I had to put a "stub within a stub" to stub the config.redisClient.get() function:
// Proxyquire allows unobstrusively overriding dependencies during testing.
// Override config used by RedisAccess.js.
var configStub = {
redisClient : {
createClient : function (port, address) {
// redis-mock-js used instead.
},
get : function (key, cb) {
if(key === 'test-rejected') {
cb(new Error('test-rejected'), 'rejected-promise');
}
else if(key === 'test-true') {
cb(null, true);
}
else if(key === 'test-get-valid') {
cb(null, 'valid-value');
}
else {
cb(new Error('Should not have gotten here!'), 'value');
}
},
}
};
which allowed me to construct this proxyquire:
var redisAccessProxy = proxyquire('lib/data/redis/RedisAccess.js', { 'config' : configStub });
and run this test using a proxy function for redisClient.get(...) which is called inside of RedisAccess.getValue(...):
var val = redisAccessProxy.getValue('test-get-valid');
assert.equal(val.isFulfilled(), true);
assert.equal(val.isRejected(), false);
assert.equal(val, 'valid-value');
I have the following strange situation. I've started writing map/reduce jobs for MongoDB. I now have code (see Map/Reduce/Finalize functions in code below) that is working, except for the "finalize" step. The same code, ran through the mongo shell is producing the expected results (file_count field is added to the result, based upon arraylength. Both JS and Shell command populate the files_per_disc collection equally (files array, per disc, contains the same results). I already tried setting jsMode to true/false.
Any help / Suggestions?
Thanks!!
Sander
var mongojs = require('mongojs');
var db = mongojs('mongodb://super.local/sha', ['files', 'files_per_disc']);
db.on('error', function(err) {
console.log('database error', err)
})
db.on('connect', function() {
console.log('database connected')
})
var files_per_disc = {};
files_per_disc.execute = function() {
console.log('execute');
var mapper = function() {
var key = this.disc; // Disc
var mapObject = {
files: [this.filename]
};
if (this.filesize > 5000000000) {
emit(key, mapObject);
}
};
var reducer = function(key, values) {
var reducedObject = {
files: []
};
values.forEach(function(value) {
value.files.forEach(function(item) {
if (reducedObject.files.indexOf(item) == -1) {
reducedObject.files.push(item);
}
});
});
return reducedObject;
};
var finalizer = function(key, reducedObject) {
if (reducedObject.files) {
reducedObject.file_count = reducedObject.files.length;
}
return reducedObject;
};
db.files.mapReduce(
mapper,
reducer, {
jsMode: false,
out: 'files_per_disc',
finalize: finalizer
},
function(err, result) {
if (err) {
return console.log(err)
};
return console.log(result);
}
);
};
module.exports = files_per_disc;
I'm starting to write tests for a node.js server I'm writing. I'm trying to test this function
exports.get_device_states = function (body, master_callback) {
var state = new State(body[State.id_key]);
console.log("STATE: ");
console.log(state);
mysqlDao.get_device_states_for_group(state, function(err, result) {
var resp_json = {
'error': err,
'stateList': result
}
master_callback(resp_json);
});
}
I need to mock the 'mysqlDao' module that I use to read/write to my db. but I was unable to do this. Whenever I try. I still get the actual db results back.
messagesTest.js
var sandbox = sinon.sandbox.create();
var stubbedDao = {
get_device_states_for_group: sandbox.stub().returns("STUBBED")
}
...
describe('messages', function() {
before(function() {
mockery.enable();
});
beforeEach(function() {
mockery.registerAllowable('async');
mockery.registerAllowable('../models/State');
mockery.registerAllowable('../models/Message');
mockery.registerMock('../dao/gcmdao', stubbedDao);
messages = require('../business/messages.js');
});
afterEach(function() {
sandbox.verifyAndRestore();
mockery.deregisterAll();
});
after(function() {
mockery.disable();
});
describe('get_device_state', function () {
it('isNormal', function () {
var body = {
"STATE_ID": testData.state_one
}
stubbedDao.get_device_states_for_group.yields();
messages.get_device_states(body, function(resp) {
console.log("returns");
assert(stubbedDao.get_device_states_for_group.calledOnce);
console.log(resp);
})
});
});
});
But instead of returning "STUBBED" Like I want it to. It just returns the Db results. It's not a huge issue for read functions but I need this to work so that I don't modify the db on other tests.
Thanks,
A.L
Updated: User count before and after signup still failing
Trying to test a new user signing up through the UI (see jQuery "signUp"). Users count from Method.call("usersCount") before and after signup both return 'undefined'.
I see 'undefined' -> user object -> 'undefined' in log. Not sure why user count is not getting assigned to the variable(s) in the spec code.
Second test checking the signup/logged-in users passes.
/tests/jasmine/client/integration/spec.js
// New user signup
function signUp (user, callback) {
$('.dropdown-toggle').trigger('click');
$('#signup-link').trigger('click');
$('#login-username').val(user.username);
$('#login-password').val(user.password);
$('#login-password-again').val(user.password);
$('#login-buttons-password').trigger('click');
callback;
}
describe('User signup', function() {
var user = { username: 'larry', password: 'password' };
beforeEach(function(done) {
Meteor.call("clearDB", done);
});
it('should increase users by one', function (done) {
var userCountBefore = Meteor.call("usersCount");
var userCountAfter = signUp(user, Meteor.call("usersCount"));
expect(userCountBefore + 1).toEqual(userCountAfter);
});
it('should automatically log-in new user', function () {
expect(Meteor.user().username).toEqual(user.username);
});
});
/packages/test-helpers.js (custom debug testing package; clearDB method from [https://gist.github.com/qnub/97d828f11c677007cb07][1])
if ((typeof process !== 'undefined') && process.env.IS_MIRROR) {
Meteor.methods({
usersCount: function () {
var count = Meteor.users.find({}).count();
return count;
},
clearDB: function(){
console.log('Clear DB');
var collectionsRemoved = 0;
var db = Meteor.users.find()._mongo.db;
db.collections(function (err, collections) {
// Filter out velocity and system.indexes from collections
var appCollections = _.reject(collections, function (col) {
return col.collectionName.indexOf('velocity') === 0 ||
col.collectionName === 'system.indexes';
});
// Remove each collection
_.each(appCollections, function (appCollection) {
appCollection.remove(function (e) {
if (e) {
console.error('Failed removing collection', e);
fut.return('fail: ' + e);
}
collectionsRemoved++;
console.log('Removed collection');
if (appCollections.length === collectionsRemoved) {
console.log('Finished resetting database');
}
});
});
});
console.log('Finished clearing');
}
});
};
Ok, this is one way to solve this:
it('should increase users by one', function (done) {
Meteor.call("usersCount", function(error, userCountBefore) {
signUp(user);
Meteor.call("usersCount", function (error, userCountAfter) {
expect(userCountAfter).toEqual(userCountBefore + 1);
done();
});
});
});
Future viewers, checkout the following links for reference/alternate approaches:
https://github.com/caolan/async
https://atmospherejs.com/peerlibrary/async
http://www.html5rocks.com/en/tutorials/es6/promises/
Thanks to #sanjo for helping me see the light!
When my page loads, I try to send a message to the server to initiate a connection, but it's not working. This script block is near the top of my file:
var connection = new WrapperWS();
connection.ident();
// var autoIdent = window.addEventListener('load', connection.ident(), false);
Most of the time, I see the error in the title:
Uncaught InvalidStateError: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state
So I tried to catch the exception, as you can see below, but now it seems InvalidStateError is not defined and that produces a ReferenceError.
Here's the wrapper object for my websocket connection:
// Define WrapperWS
function WrapperWS() {
if ("WebSocket" in window) {
var ws = new WebSocket("ws://server:8000/");
var self = this;
ws.onopen = function () {
console.log("Opening a connection...");
window.identified = false;
};
ws.onclose = function (evt) {
console.log("I'm sorry. Bye!");
};
ws.onmessage = function (evt) {
// handle messages here
};
ws.onerror = function (evt) {
console.log("ERR: " + evt.data);
};
this.write = function () {
if (!window.identified) {
connection.ident();
console.debug("Wasn't identified earlier. It is now.");
}
ws.send(theText.value);
};
this.ident = function () {
var session = "Test";
try {
ws.send(session);
} catch (error) {
if (error instanceof InvalidStateError) {
// possibly still 'CONNECTING'
if (ws.readyState !== 1) {
var waitSend = setInterval(ws.send(session), 1000);
}
}
}
window.identified = true;
theText.value = "Hello!";
say.click();
theText.disabled = false;
};
};
}
I am testing using Chromium on Ubuntu.
You could send messages via a proxy function that waits for the readyState to be 1.
this.send = function (message, callback) {
this.waitForConnection(function () {
ws.send(message);
if (typeof callback !== 'undefined') {
callback();
}
}, 1000);
};
this.waitForConnection = function (callback, interval) {
if (ws.readyState === 1) {
callback();
} else {
var that = this;
// optional: implement backoff for interval here
setTimeout(function () {
that.waitForConnection(callback, interval);
}, interval);
}
};
Then use this.send in place of ws.send, and put the code that should be run afterwards in a callback:
this.ident = function () {
var session = "Test";
this.send(session, function () {
window.identified = true;
theText.value = "Hello!";
say.click();
theText.disabled = false;
});
};
For something more streamlined you could look into promises.
This error is raised because you are sending your message before the WebSocket connection is established.
You can solve it by doing this simply:
conn.onopen = () => conn.send("Message");
This onopen function waits for your WebSocket connection to establish before sending your message.
if you use one websocket client object and connect from random app places then object can be in connecting mode (concurent access).
if you want to exchange through only one websoket then
create class with promise and keep it in property
class Ws {
get newClientPromise() {
return new Promise((resolve, reject) => {
let wsClient = new WebSocket("ws://demos.kaazing.com/echo");
console.log(wsClient)
wsClient.onopen = () => {
console.log("connected");
resolve(wsClient);
};
wsClient.onerror = error => reject(error);
})
}
get clientPromise() {
if (!this.promise) {
this.promise = this.newClientPromise
}
return this.promise;
}
}
create singleton
window.wsSingleton = new Ws()
use clientPromise property in any place of app
window.wsSingleton.clientPromise
.then( wsClient =>{wsClient.send('data'); console.log('sended')})
.catch( error => alert(error) )
http://jsfiddle.net/adqu7q58/11/
Method 1: Check connection
You can resolve a promise when socket is connected:
async function send(data) {
await checkConnection();
ws.send(data);
}
Implementation
This trick is implemented using an array of resolvers.
let ws = new WebSocket(url);
let connection_resolvers = [];
let checkConnection = () => {
return new Promise((resolve, reject) => {
if (ws.readyState === WebSocket.OPEN) {
resolve();
}
else {
connection_resolvers.push({resolve, reject});
}
});
}
ws.addEventListener('open', () => {
connection_resolvers.forEach(r => r.resolve())
});
Method 2: Wait for connection
You can resolve a promise when socket is not connected:
const MAX_RETRIES = 4;
async function send(data, retries = 0) {
try {
ws.send(data);
}
catch (error) {
if (retries < MAX_RETRIES error.name === "InvalidStateError") {
await waitForConnection();
send(data, retries + 1);
}
else {
throw error;
}
}
}
Implementation
This trick is implemented using an array of resolvers.
let ws = new WebSocket(url);
let connection_resolvers = [];
let waitForConnection = () => {
return new Promise((resolve, reject) => {
connection_resolvers.push({resolve, reject});
});
}
ws.addEventListener('open', () => {
connection_resolvers.forEach(r => r.resolve())
});
My opinion is that the second method has a little bit good performance!
It is possible to use functions and readyState with setTimeout.
function openSocket()
{
webSocket = new WebSocket("");
}
function sendData()
{
if(webSocket.readyState)
{
webSocket.send(JSON.stringify(
{
"event" : "",
"message" : ""
}));
}
else
{
setTimeout(sendData, 1000);
}
}
function eventHandler()
{
webSocket.onmessage = function(e)
{
data = JSON.parse(e.data);
event = data.event;
switch (event)
{...}
}
}
openSocket();
sendData();
enentHandler();