/*jshint globalstrict:false, strict:true */ /*global assertEqual, assertTrue, ARGUMENTS */ //////////////////////////////////////////////////////////////////////////////// /// @brief tests for client-specific functionality /// /// @file /// /// DISCLAIMER /// /// Copyright 2010-2016 triagens GmbH, Cologne, Germany /// /// 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. /// /// Copyright holder is triAGENS GmbH, Cologne, Germany /// /// @author Max Neunhoeffer /// @author Copyright 2016, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// var jsunity = require("jsunity"); var wait = require("internal").wait; var _ = require("lodash"); //////////////////////////////////////////////////////////////////////////////// /// @brief bogus UUIDs //////////////////////////////////////////////////////////////////////////////// function guid() { function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); } //////////////////////////////////////////////////////////////////////////////// /// @brief shuffle array elements //////////////////////////////////////////////////////////////////////////////// function shuffle(a) { for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } } //////////////////////////////////////////////////////////////////////////////// /// @brief test suite //////////////////////////////////////////////////////////////////////////////// function agencyTestSuite () { 'use strict'; //////////////////////////////////////////////////////////////////////////////// /// @brief the agency servers //////////////////////////////////////////////////////////////////////////////// var instanceInfo = JSON.parse(require('internal').env.INSTANCEINFO); var agencyServers = instanceInfo.arangods.map(arangod => { return arangod.url; }); var agencyLeader = agencyServers[0]; var request = require("@arangodb/request"); function agencyConfig() { for (let count = 0; count < 60; ++count) { let res = request({url: agencyLeader + "/_api/agency/config", method: "GET", followRedirect: true}); if (res.statusCode === 200) { res.bodyParsed = JSON.parse(res.body); if (res.bodyParsed.leaderId !== "") { return res.bodyParsed; } require('console').topic("agency=warn", "No leadership ..."); } else { require('console').topic("agency=warn", "Got status " + res.statusCode + " from agency."); } require("internal").wait(1.0); // give the server another second } require('console').topic("agency=error", "Giving up, agency did not boot successfully."); assertEqual("apple", "orange"); // all is lost because agency did not get up and running in time } function findAgencyCompactionIntervals() { var c = agencyConfig(); return { compactionStepSize: c.configuration["compaction step size"], compactionKeepSize: c.configuration["compaction keep size"] }; } var compactionConfig = findAgencyCompactionIntervals(); require("console").topic("agency=info", "Agency compaction configuration: ", compactionConfig); function getCompactions(servers) { var ret = []; servers.forEach(function (url) { var compaction = { url: url + "/_api/cursor", timeout: 240, method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({ query : "FOR c IN compact SORT c._key RETURN c" })}; var state = { url: url + "/_api/agency/state", timeout: 240 }; ret.push({compactions: JSON.parse(request(compaction).body), state: JSON.parse(request(state).body), url: url}); }); return ret; } function accessAgency(api, list, timeout = 60) { // We simply try all agency servers in turn until one gives us an HTTP // response: var res; var inquire = false; var clientIds = []; list.forEach(function (trx) { if (Array.isArray(trx) && trx.length === 3 && typeof(trx[0]) === 'object' && typeof(trx[2]) === 'string') { clientIds.push(trx[2]); } }); var startTime = new Date(); while (true) { if (new Date() - startTime > 600000) { assertTrue(false, "Hit global timeout of 10 minutes in accessAgency."); } if (!inquire) { res = request({url: agencyLeader + "/_api/agency/" + api, method: "POST", followRedirect: false, body: JSON.stringify(list), headers: {"Content-Type": "application/json"}, timeout: timeout /* essentially for the huge trx package running under ASAN in the CI */ }); require('console').topic("agency=debug", 'Sent out agency request, statusCode:', res.statusCode); } else { // inquire. Remove successful commits. For later retries res = request({url: agencyLeader + "/_api/agency/inquire", method: "POST", followRedirect: false, body: JSON.stringify(clientIds), headers: {"Content-Type": "application/json"}, timeout: timeout }); require('console').topic("agency=info", 'Sent out agency inquiry, statusCode:', res.statusCode); } if (res.statusCode === 307) { agencyLeader = res.headers.location; var l = 0; for (var i = 0; i < 3; ++i) { l = agencyLeader.indexOf('/', l+1); } agencyLeader = agencyLeader.substring(0,l); if (clientIds.length > 0 && api === 'write') { inquire = true; } require('console').topic("agency=info", 'Redirected to ' + agencyLeader); continue; } else if (res.statusCode === 503 || res.statusCode === 500) { // 503 covers service not available and 500 covers timeout require('console').topic("agency=info", 'Got status code', res.statusCode, ', waiting for leader ... '); if (clientIds.length > 0 && api === 'write') { inquire = true; } wait(1.0); continue; } if (!inquire) { break; // done, let's report the result, whatever it is } // In case of inquiry, we probably have done some of the transactions: var done = 0; res.bodyParsed = JSON.parse(res.body); res.bodyParsed.results.forEach(function (index) { var noZeroYet = true; if (index > 0) { done++; assertTrue(noZeroYet); } else { noZeroYet = false; } }); require('console').topic("agency=info", 'Inquiry analysis: done=', done, ' body:', res.body); if (done === clientIds.length) { require('console').topic("agency=info", 'Inquiry analysis, accepting result as good!'); break; } else { list = list.slice(done); clientIds = clientIds.slice(done); inquire = false; require('console').topic("agency=info", 'Inquiry analysis: have accepted', done, 'transactions as done, continuing with this list:', JSON.stringify(list)); } } try { res.bodyParsed = JSON.parse(res.body); } catch(e) { require("console").error("Exception in body parse:", res.body, JSON.stringify(e), api, list, JSON.stringify(res)); } return res; } function readAndCheck(list) { var res = accessAgency("read", list); assertEqual(res.statusCode, 200); return res.bodyParsed; } function writeAndCheck(list, timeout = 60) { var res = accessAgency("write", list, timeout); assertEqual(res.statusCode, 200); return res.bodyParsed; } function transactAndCheck(list, code) { var res = accessAgency("transact", list); assertEqual(res.statusCode, code); return res.bodyParsed; } function doCountTransactions(count, start) { let i, res; let counter = 0; let trxs = []; for (i = start; i < start + count; ++i) { let key = "/key"+i; let trx = [{},{},"clientid" + start + counter++]; trx[0][key] = "value" + i; trxs.push(trx); if (trxs.length >= 200 || i === start + count - 1) { res = accessAgency("write", trxs); assertEqual(200, res.statusCode); trxs = []; } } trxs = []; for (i = 0; i < start + count; ++i) { trxs.push(["/key"+i]); } res = accessAgency("read", trxs); assertEqual(200, res.statusCode); for (i = 0; i < start + count; ++i) { let key = "key"+i; let correct = {}; correct[key] = "value" + i; assertEqual(correct, res.bodyParsed[i]); } } function evalComp() { var servers = _.clone(agencyServers), llogi; var count = 0; while (servers.length > 0) { var agents = getCompactions(servers), i, old; var ready = true; for (i = 1; i < agents.length; ++i) { if (agents[0].state.log[agents[0].state.log.length-1].index !== agents[i].state.log[agents[i].state.log.length-1].index) { ready = false; break; } } if (!ready) { continue; } agents.forEach( function (agent) { var results = agent.compactions.result; // All compactions var llog = agent.state.log[agent.state.log.length-1]; // Last log entry llogi = llog.index; // Last log index var lcomp = results[results.length-1]; // Last compaction entry var lcompi = parseInt(lcomp._key); // Last compaction index var stepsize = compactionConfig.compactionStepSize; if (lcompi > llogi - stepsize) { // agent has compacted var foobar = accessAgency("read", [["foobar"]]).bodyParsed[0].foobar; var n = 0; var keepsize = compactionConfig.compactionKeepSize; var flog = agent.state.log[0]; // First log entry var flogi = flog.index; // First log index // Expect to find last compaction maximally // keep-size away from last RAFT index assertTrue(lcompi > llogi - stepsize); // log entries before compaction index - compaction keep size // are dumped if (lcompi > keepsize) { assertTrue(flogi === lcompi - keepsize); } else { assertEqual(flogi, 0); } if(lcomp.readDB[0].hasOwnProperty("foobar")) { // All log entries > last compaction index, // which are {"foobar":{"op":"increment"}} agent.state.log.forEach( function(log) { if (log.index > lcompi) { if (log.query.foobar !== undefined) { ++n; } } }); // Sum of relevant log entries > last compaction index and last // compaction's foobar value must match foobar's value in agency assertEqual(lcomp.readDB[0].foobar + n, foobar); } // this agent is fine remove it from agents to be check this time // around list servers.splice(servers.indexOf(agent.url)); } }); wait(0.1); ++count; if (count > 600) { return 0; } } return llogi; } return { //////////////////////////////////////////////////////////////////////////////// /// @brief set up //////////////////////////////////////////////////////////////////////////////// setUp : function () { }, //////////////////////////////////////////////////////////////////////////////// /// @brief tear down //////////////////////////////////////////////////////////////////////////////// tearDown : function () { }, //////////////////////////////////////////////////////////////////////////////// /// @brief wait until leadership is established //////////////////////////////////////////////////////////////////////////////// testStartup : function () { assertEqual(readAndCheck([["/"]]), [{}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test transact interface //////////////////////////////////////////////////////////////////////////////// testTransact : function () { var cur = accessAgency("write",[[{"/": {"op":"delete"}}]]). bodyParsed.results[0]; assertEqual(readAndCheck([["/x"]]), [{}]); var res = transactAndCheck([["/x"],[{"/x":12}]],200); assertEqual(res, [{},++cur]); res = transactAndCheck([["/x"],[{"/x":12}]],200); assertEqual(res, [{x:12},++cur]); res = transactAndCheck([["/x"],[{"/x":12}],["/x"]],200); assertEqual(res, [{x:12},++cur,{x:12}]); res = transactAndCheck([["/x"],[{"/x":12}],["/x"]],200); assertEqual(res, [{x:12},++cur,{x:12}]); res = transactAndCheck([["/x"],[{"/x":{"op":"increment"}}],["/x"]],200); assertEqual(res, [{x:12},++cur,{x:13}]); res = transactAndCheck( [["/x"],[{"/x":{"op":"increment"}}],["/x"],[{"/x":{"op":"increment"}}]], 200); assertEqual(res, [{x:13},++cur,{x:14},++cur]); res = transactAndCheck( [[{"/x":{"op":"increment"}}],[{"/x":{"op":"increment"}}],["/x"]],200); assertEqual(res, [++cur,++cur,{x:17}]); writeAndCheck([[{"/":{"op":"delete"}}]]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief test to write a single top level key //////////////////////////////////////////////////////////////////////////////// testSingleTopLevel : function () { assertEqual(readAndCheck([["/x"]]), [{}]); writeAndCheck([[{x:12}]]); assertEqual(readAndCheck([["/x"]]), [{x:12}]); writeAndCheck([[{x:{"op":"delete"}}]]); assertEqual(readAndCheck([["/x"]]), [{}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief test to write a single non-top level key //////////////////////////////////////////////////////////////////////////////// testSingleNonTopLevel : function () { assertEqual(readAndCheck([["/x/y"]]), [{}]); writeAndCheck([[{"x/y":12}]]); assertEqual(readAndCheck([["/x/y"]]), [{x:{y:12}}]); writeAndCheck([[{"x/y":{"op":"delete"}}]]); assertEqual(readAndCheck([["/x"]]), [{x:{}}]); writeAndCheck([[{"x":{"op":"delete"}}]]); assertEqual(readAndCheck([["/x"]]), [{}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief test preconditions //////////////////////////////////////////////////////////////////////////////// testPrecondition : function () { writeAndCheck([[{"/a":12}]]); assertEqual(readAndCheck([["/a"]]), [{a:12}]); writeAndCheck([[{"/a":13},{"/a":12}]]); assertEqual(readAndCheck([["/a"]]), [{a:13}]); var res = accessAgency("write", [[{"/a":14},{"/a":12}]]); // fail precond {a:12} assertEqual(res.statusCode, 412); assertEqual(res.bodyParsed, {"results":[0]}); writeAndCheck([[{a:{op:"delete"}}]]); // fail precond oldEmpty res = accessAgency("write",[[{"a":14},{"a":{"oldEmpty":false}}]]); assertEqual(res.statusCode, 412); assertEqual(res.bodyParsed, {"results":[0]}); writeAndCheck([[{"a":14},{"a":{"oldEmpty":true}}]]); // precond oldEmpty writeAndCheck([[{"a":14},{"a":{"old":14}}]]); // precond old // fail precond old res = accessAgency("write",[[{"a":14},{"a":{"old":13}}]]); assertEqual(res.statusCode, 412); assertEqual(res.bodyParsed, {"results":[0]}); writeAndCheck([[{"a":14},{"a":{"isArray":false}}]]); // precond isArray // fail precond isArray res = accessAgency("write",[[{"a":14},{"a":{"isArray":true}}]]); assertEqual(res.statusCode, 412); assertEqual(res.bodyParsed, {"results":[0]}); // check object precondition res = accessAgency("write",[[{"/a/b/c":{"op":"set","new":12}}]]); res = accessAgency("write",[[{"/a/b/c":{"op":"set","new":13}},{"a":{"old":{"b":{"c":12}}}}]]); assertEqual(res.statusCode, 200); res = accessAgency("write",[[{"/a/b/c":{"op":"set","new":14}},{"/a":{"old":{"b":{"c":12}}}}]]); assertEqual(res.statusCode, 412); res = accessAgency("write",[[{"/a/b/c":{"op":"set","new":14}},{"/a":{"old":{"b":{"c":13}}}}]]); assertEqual(res.statusCode, 200); // multiple preconditions res = accessAgency("write",[[{"/a":1,"/b":true,"/c":"c"},{"/a":{"oldEmpty":false}}]]); assertEqual(readAndCheck([["/a","/b","c"]]), [{a:1,b:true,c:"c"}]); res = accessAgency("write",[[{"/a":2},{"/a":{"oldEmpty":false},"/b":{"oldEmpty":true}}]]); assertEqual(res.statusCode, 412); assertEqual(readAndCheck([["/a"]]), [{a:1}]); res = accessAgency("write",[[{"/a":2},{"/a":{"oldEmpty":true},"/b":{"oldEmpty":false}}]]); assertEqual(res.statusCode, 412); assertEqual(readAndCheck([["/a"]]), [{a:1}]); res = accessAgency("write",[[{"/a":2},{"/a":{"oldEmpty":false},"/b":{"oldEmpty":false},"/c":{"oldEmpty":true}}]]); assertEqual(res.statusCode, 412); assertEqual(readAndCheck([["/a"]]), [{a:1}]); res = accessAgency("write",[[{"/a":2},{"/a":{"oldEmpty":false},"/b":{"oldEmpty":false},"/c":{"oldEmpty":false}}]]); assertEqual(res.statusCode, 200); assertEqual(readAndCheck([["/a"]]), [{a:2}]); res = accessAgency("write",[[{"/a":3},{"/a":{"old":2},"/b":{"oldEmpty":false},"/c":{"oldEmpty":false}}]]); assertEqual(res.statusCode, 200); assertEqual(readAndCheck([["/a"]]), [{a:3}]); res = accessAgency("write",[[{"/a":2},{"/a":{"old":2},"/b":{"oldEmpty":false},"/c":{"oldEmpty":false}}]]); assertEqual(res.statusCode, 412); assertEqual(readAndCheck([["/a"]]), [{a:3}]); res = accessAgency("write",[[{"/a":2},{"/a":{"old":3},"/b":{"oldEmpty":false},"/c":{"isArray":true}}]]); assertEqual(res.statusCode, 412); assertEqual(readAndCheck([["/a"]]), [{a:3}]); res = accessAgency("write",[[{"/a":2},{"/a":{"old":3},"/b":{"oldEmpty":false},"/c":{"isArray":false}}]]); assertEqual(res.statusCode, 200); assertEqual(readAndCheck([["/a"]]), [{a:2}]); // in precondition & multiple writeAndCheck([[{"a":{"b":{"c":[1,2,3]},"e":[1,2]},"d":false}]]); res = accessAgency("write",[[{"/b":2},{"/a/b/c":{"in":3}}]]); assertEqual(res.statusCode, 200); assertEqual(readAndCheck([["/b"]]), [{b:2}]); res = accessAgency("write",[[{"/b":3},{"/a/e":{"in":3}}]]); assertEqual(res.statusCode, 412); assertEqual(readAndCheck([["/b"]]), [{b:2}]); res = accessAgency("write",[[{"/b":3},{"/a/e":{"in":3},"/a/b/c":{"in":3}}]]); assertEqual(res.statusCode, 412); res = accessAgency("write",[[{"/b":3},{"/a/e":{"in":3},"/a/b/c":{"in":3}}]]); assertEqual(res.statusCode, 412); res = accessAgency("write",[[{"/b":3},{"/a/b/c":{"in":3},"/a/e":{"in":3}}]]); assertEqual(res.statusCode, 412); res = accessAgency("write",[[{"/b":3},{"/a/b/c":{"in":3},"/a/e":{"in":2}}]]); assertEqual(res.statusCode, 200); assertEqual(readAndCheck([["/b"]]), [{b:3}]); // Permute order of keys and objects within precondition var localObj = {"foo" : "bar", "baz" : { "_id": "5a00203e4b660989b2ae5493", "index": 0, "guid": "7a709cc2-1479-4079-a0a3-009cbe5674f4", "isActive": true, "balance": "$3,072.23", "picture": "http://placehold.it/32x32", "age": 21, "eyeColor": "green", "name": { "first": "Durham", "last": "Duke" }, "tags": ["anim","et","id","do","est",1.0,-1024,1024] }, "qux" : ["3.14159265359",3.14159265359] }; var test; var localKeys = []; for (var i in localObj.baz) { localKeys.push(i); } var permuted; res = accessAgency( "write", [[localObj, {"foo":localObj.bar, "baz":{"old":localObj.baz}, "qux":localObj.qux}]]); assertEqual(res.statusCode, 412); res = writeAndCheck([[localObj]]); res = writeAndCheck([[localObj, {"foo":localObj.foo,"baz":{"old":localObj.baz},"qux":localObj.qux}]]); res = writeAndCheck( [[localObj, {"baz":{"old":localObj.baz},"foo":localObj.foo,"qux":localObj.qux}]]); res = writeAndCheck( [[localObj, {"baz":{"old":localObj.baz},"qux":localObj.qux,"foo":localObj.foo}]]); res = writeAndCheck( [[localObj, {"qux":localObj.qux,"baz":{"old":localObj.baz},"foo":localObj.foo}]]); for (var j in localKeys) { permuted = {}; shuffle(localKeys); for (var k in localKeys) { permuted[localKeys[k]] = localObj.baz[localKeys[k]]; } res = writeAndCheck( [[localObj, {"baz":{"old":permuted},"foo":localObj.foo,"qux":localObj.qux}]]); res = writeAndCheck( [[localObj, {"foo":localObj.foo,"qux":localObj.qux,"baz":{"old":permuted}}]]); res = writeAndCheck( [[localObj, {"qux":localObj.qux,"baz":{"old":permuted},"foo":localObj.foo}]]); } // Permute order of keys and objects within arrays in preconditions writeAndCheck([[{"a":[{"b":12,"c":13}]}]]); writeAndCheck([[{"a":[{"b":12,"c":13}]},{"a":[{"b":12,"c":13}]}]]); writeAndCheck([[{"a":[{"b":12,"c":13}]},{"a":[{"c":13,"b":12}]}]]); localObj = {"b":"Hello world!", "c":3.14159265359, "d":314159265359, "e": -3}; var localObk = {"b":1, "c":1.0, "d": 100000000001, "e": -1}; localKeys = []; for (var l in localObj) { localKeys.push(l); } permuted = {}; var per2 = {}; writeAndCheck([[ { "a" : [localObj,localObk] } ]]); writeAndCheck([[ { "a" : [localObj,localObk] }, {"a" : [localObj,localObk] }]]); for (var m = 0; m < 7; m++) { permuted = {}; shuffle(localKeys); for (k in localKeys) { permuted[localKeys[k]] = localObj[localKeys[k]]; per2 [localKeys[k]] = localObk[localKeys[k]]; } writeAndCheck([[ { "a" : [localObj,localObk] }, {"a" : [permuted,per2] }]]); res = accessAgency("write", [[ { "a" : [localObj,localObk] }, {"a" : [per2,permuted] }]]); assertEqual(res.statusCode, 412); } }, //////////////////////////////////////////////////////////////////////////////// /// @brief test clientIds //////////////////////////////////////////////////////////////////////////////// testClientIds : function () { var res; var cur; res = accessAgency("write", [[{"a":12}]]).bodyParsed; cur = res.results[0]; writeAndCheck([[{"/a":12}]]); var id = [guid(),guid(),guid(),guid(),guid(),guid(), guid(),guid(),guid(),guid(),guid(),guid(), guid(),guid(),guid()]; var query = [{"a":12},{"a":13},{"a":13}]; var pre = [{},{"a":12},{"a":12}]; cur += 2; var wres = accessAgency("write", [[query[0], pre[0], id[0]]]); res = accessAgency("inquire",[id[0]]); wres.bodyParsed.inquired = true; assertEqual(res.bodyParsed.results, wres.bodyParsed.results); wres = accessAgency("write", [[query[1], pre[1], id[0]]]); res = accessAgency("inquire",[id[0]]); assertEqual(res.bodyParsed.results, wres.bodyParsed.results); cur++; wres = accessAgency("write",[[query[1], pre[1], id[2]]]); assertEqual(wres.statusCode,412); res = accessAgency("inquire",[id[2]]); assertEqual(res.statusCode,404); assertEqual(res.bodyParsed, {"results":[0],"inquired":true}); assertEqual(res.bodyParsed.results, wres.bodyParsed.results); wres = accessAgency("write",[[query[0], pre[0], id[3]], [query[1], pre[1], id[3]]]); assertEqual(wres.statusCode,200); cur += 2; res = accessAgency("inquire",[id[3]]); assertEqual(res.bodyParsed, {"results":[cur],"inquired":true}); assertEqual(res.bodyParsed.results[0], wres.bodyParsed.results[1]); assertEqual(res.statusCode,200); wres = accessAgency("write",[[query[0], pre[0], id[4]], [query[1], pre[1], id[4]], [query[2], pre[2], id[4]]]); assertEqual(wres.statusCode,412); cur += 2; res = accessAgency("inquire",[id[4]]); assertEqual(res.bodyParsed, {"results":[cur],"inquired":true}); assertEqual(res.bodyParsed.results[0], wres.bodyParsed.results[1]); assertEqual(res.statusCode,200); wres = accessAgency("write",[[query[0], pre[0], id[5]], [query[2], pre[2], id[5]], [query[1], pre[1], id[5]]]); assertEqual(wres.statusCode,412); cur += 2; res = accessAgency("inquire",[id[5]]); assertEqual(res.bodyParsed, {"results":[cur],"inquired":true}); assertEqual(res.bodyParsed.results[0], wres.bodyParsed.results[1]); assertEqual(res.statusCode,200); wres = accessAgency("write",[[query[2], pre[2], id[6]], [query[0], pre[0], id[6]], [query[1], pre[1], id[6]]]); assertEqual(wres.statusCode,412); cur += 2; res = accessAgency("inquire",[id[6]]); assertEqual(res.bodyParsed, {"results":[cur],"inquired":true}); assertEqual(res.bodyParsed.results[0], wres.bodyParsed.results[2]); assertEqual(res.statusCode,200); wres = accessAgency("write",[[query[2], pre[2], id[7]], [query[0], pre[0], id[8]], [query[1], pre[1], id[9]]]); assertEqual(wres.statusCode,412); cur += 2; res = accessAgency("inquire",[id[7],id[8],id[9]]); assertEqual(res.statusCode,404); assertEqual(res.bodyParsed.results, wres.bodyParsed.results); }, //////////////////////////////////////////////////////////////////////////////// /// @brief test document/transaction assignment //////////////////////////////////////////////////////////////////////////////// testDocument : function () { writeAndCheck([[{"a":{"b":{"c":[1,2,3]},"e":12},"d":false}]]); assertEqual(readAndCheck([["a/e"],[ "d","a/b"]]), [{a:{e:12}},{a:{b:{c:[1,2,3]},d:false}}]); writeAndCheck([[{"a":{"_id":"576d1b7becb6374e24ed5a04","index":0,"guid":"60ffa50e-0211-4c60-a305-dcc8063ae2a5","isActive":true,"balance":"$1,050.96","picture":"http://placehold.it/32x32","age":30,"eyeColor":"green","name":{"first":"Maura","last":"Rogers"},"company":"GENESYNK","email":"maura.rogers@genesynk.net","phone":"+1(804)424-2766","address":"501RiverStreet,Wollochet,Vermont,6410","about":"Temporsintofficiaipsumidnullalaboreminimlaborisinlaborumincididuntexcepteurdolore.Sunteumagnadolaborumsunteaquisipsumaliquaaliquamagnaminim.Cupidatatadproidentullamconisietofficianisivelitculpaexcepteurqui.Suntautemollitconsecteturnulla.Commodoquisidmagnaestsitelitconsequatdoloreupariaturaliquaetid.","registered":"Friday,November28,20148:01AM","latitude":"-30.093679","longitude":"10.469577","tags":["laborum","proident","est","veniam","sunt"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"CarverDurham"},{"id":1,"name":"DanielleMalone"},{"id":2,"name":"ViolaBell"}],"greeting":"Hello,Maura!Youhave9unreadmessages.","favoriteFruit":"banana"}}],[{"!!@#$%^&*)":{"_id":"576d1b7bb2c1af32dd964c22","index":1,"guid":"e6bda5a9-54e3-48ea-afd7-54915fec48c2","isActive":false,"balance":"$2,631.75","picture":"http://placehold.it/32x32","age":40,"eyeColor":"blue","name":{"first":"Jolene","last":"Todd"},"company":"QUANTASIS","email":"jolene.todd@quantasis.us","phone":"+1(954)418-2311","address":"818ButlerStreet,Berwind,Colorado,2490","about":"Commodoesseveniamadestirureutaliquipduistempor.Auteeuametsuntessenisidolorfugiatcupidatatsintnulla.Sitanimincididuntelitculpasunt.","registered":"Thursday,June12,201412:08AM","latitude":"-7.101063","longitude":"4.105685","tags":["ea","est","sunt","proident","pariatur"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"SwansonMcpherson"},{"id":1,"name":"YoungTyson"},{"id":2,"name":"HinesSandoval"}],"greeting":"Hello,Jolene!Youhave5unreadmessages.","favoriteFruit":"strawberry"}}],[{"1234567890":{"_id":"576d1b7b79527b6201ed160c","index":2,"guid":"2d2d7a45-f931-4202-853d-563af252ca13","isActive":true,"balance":"$1,446.93","picture":"http://placehold.it/32x32","age":28,"eyeColor":"blue","name":{"first":"Pickett","last":"York"},"company":"ECSTASIA","email":"pickett.york@ecstasia.me","phone":"+1(901)571-3225","address":"556GrovePlace,Stouchsburg,Florida,9119","about":"Idnulladolorincididuntirurepariaturlaborumutmolliteavelitnonveniaminaliquip.Adametirureesseanimindoloreduisproidentdeserunteaconsecteturincididuntconsecteturminim.Ullamcoessedolorelitextemporexcepteurexcepteurlaboreipsumestquispariaturmagna.ExcepteurpariaturexcepteuradlaborissitquieiusmodmagnalaborisincididuntLoremLoremoccaecat.","registered":"Thursday,January28,20165:20PM","latitude":"-56.18036","longitude":"-39.088125","tags":["ad","velit","fugiat","deserunt","sint"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"BarryCleveland"},{"id":1,"name":"KiddWare"},{"id":2,"name":"LangBrooks"}],"greeting":"Hello,Pickett!Youhave10unreadmessages.","favoriteFruit":"strawberry"}}],[{"@":{"_id":"576d1b7bc674d071a2bccc05","index":3,"guid":"14b44274-45c2-4fd4-8c86-476a286cb7a2","isActive":true,"balance":"$1,861.79","picture":"http://placehold.it/32x32","age":27,"eyeColor":"brown","name":{"first":"Felecia","last":"Baird"},"company":"SYBIXTEX","email":"felecia.baird@sybixtex.name","phone":"+1(821)498-2971","address":"571HarrisonAvenue,Roulette,Missouri,9284","about":"Adesseofficianisiexercitationexcepteurametconsecteturessequialiquaquicupidatatincididunt.Nostrudullamcoutlaboreipsumduis.ConsequatsuntlaborumadLoremeaametveniamesseoccaecat.","registered":"Monday,December21,20156:50AM","latitude":"0.046813","longitude":"-13.86172","tags":["velit","qui","ut","aliquip","eiusmod"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"CeliaLucas"},{"id":1,"name":"HensonKline"},{"id":2,"name":"ElliottWalker"}],"greeting":"Hello,Felecia!Youhave9unreadmessages.","favoriteFruit":"apple"}}],[{"|}{[]αв¢∂єƒgαв¢∂єƒg":{"_id":"576d1b7be4096344db437417","index":4,"guid":"f789235d-b786-459f-9288-0d2f53058d02","isActive":false,"balance":"$2,011.07","picture":"http://placehold.it/32x32","age":28,"eyeColor":"brown","name":{"first":"Haney","last":"Burks"},"company":"SPACEWAX","email":"haney.burks@spacewax.info","phone":"+1(986)587-2735","address":"197OtsegoStreet,Chesterfield,Delaware,5551","about":"Quisirurenostrudcupidatatconsequatfugiatvoluptateproidentvoluptate.Duisnullaadipisicingofficiacillumsuntlaborisdeseruntirure.Laborumconsecteturelitreprehenderitestcillumlaboresintestnisiet.Suntdeseruntexercitationutauteduisaliquaametetquisvelitconsecteturirure.Auteipsumminimoccaecatincididuntaute.Irureenimcupidatatexercitationutad.Minimconsecteturadipisicingcommodoanim.","registered":"Friday,January16,20155:29AM","latitude":"86.036358","longitude":"-1.645066","tags":["occaecat","laboris","ipsum","culpa","est"],"range":[0,1,2,3,4,5,6,7,8,9],"friends":[{"id":0,"name":"SusannePacheco"},{"id":1,"name":"SpearsBerry"},{"id":2,"name":"VelazquezBoyle"}],"greeting":"Hello,Haney!Youhave10unreadmessages.","favoriteFruit":"apple"}}]]); assertEqual(readAndCheck([["/!!@#$%^&*)/address"]]),[{"!!@#$%^&*)":{"address": "818ButlerStreet,Berwind,Colorado,2490"}}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief test arrays //////////////////////////////////////////////////////////////////////////////// testArrays : function () { writeAndCheck([[{"/":[]}]]); assertEqual(readAndCheck([["/"]]),[[]]); writeAndCheck([[{"/":[1,2,3]}]]); assertEqual(readAndCheck([["/"]]),[[1,2,3]]); writeAndCheck([[{"/a":[1,2,3]}]]); assertEqual(readAndCheck([["/"]]),[{a:[1,2,3]}]); writeAndCheck([[{"1":["C","C++","Java","Python"]}]]); assertEqual(readAndCheck([["/1"]]),[{1:["C","C++","Java","Python"]}]); writeAndCheck([[{"1":["C",2.0,"Java","Python"]}]]); assertEqual(readAndCheck([["/1"]]),[{1:["C",2.0,"Java","Python"]}]); writeAndCheck([[{"1":["C",2.0,"Java",{"op":"set","new":12,"ttl":7}]}]]); assertEqual(readAndCheck([["/1"]]),[{"1":["C",2,"Java",{"op":"set","new":12,"ttl":7}]}]); writeAndCheck([[{"1":["C",2.0,"Java",{"op":"set","new":12,"ttl":7,"Array":[12,3]}]}]]); assertEqual(readAndCheck([["/1"]]),[{"1":["C",2,"Java",{"op":"set","new":12,"ttl":7,"Array":[12,3]}]}]); writeAndCheck([[{"2":[[],[],[],[],[[[[[]]]]]]}]]); assertEqual(readAndCheck([["/2"]]),[{"2":[[],[],[],[],[[[[[]]]]]]}]); writeAndCheck([[{"2":[[[[[[]]]]],[],[],[],[[]]]}]]); assertEqual(readAndCheck([["/2"]]),[{"2":[[[[[[]]]]],[],[],[],[[]]]}]); writeAndCheck([[{"2":[[[[[["Hello World"],"Hello World"],1],2.0],"C"],[1],[2],[3],[[1,2],3],4]}]]); assertEqual(readAndCheck([["/2"]]),[{"2":[[[[[["Hello World"],"Hello World"],1],2.0],"C"],[1],[2],[3],[[1,2],3],4]}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief test multiple transaction //////////////////////////////////////////////////////////////////////////////// testTransaction : function () { writeAndCheck([[{"a":{"b":{"c":[1,2,4]},"e":12},"d":false}], [{"a":{"b":{"c":[1,2,3]}}}]]); assertEqual(readAndCheck([["a/e"],[ "d","a/b"]]), [{a:{}},{a:{b:{c:[1,2,3]},d:false}}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test "new" operator //////////////////////////////////////////////////////////////////////////////// testOpSetNew : function () { writeAndCheck([[{"a/z":{"op":"set","new":12}}]]); assertEqual(readAndCheck([["/a/z"]]), [{"a":{"z":12}}]); writeAndCheck([[{"a/y":{"op":"set","new":12, "ttl": 1}}]]); assertEqual(readAndCheck([["/a/y"]]), [{"a":{"y":12}}]); wait(1.1); assertEqual(readAndCheck([["/a/y"]]), [{a:{}}]); writeAndCheck([[{"/a/y":{"op":"set","new":12, "ttl": 1}}]]); assertEqual(readAndCheck([["a/y"]]), [{"a":{"y":12}}]); wait(1.1); assertEqual(readAndCheck([["/a/y"]]), [{a:{}}]); writeAndCheck([[{"foo/bar":{"op":"set","new":{"baz":12}}}]]); assertEqual(readAndCheck([["/foo/bar/baz"]]), [{"foo":{"bar":{"baz":12}}}]); assertEqual(readAndCheck([["/foo/bar"]]), [{"foo":{"bar":{"baz":12}}}]); assertEqual(readAndCheck([["/foo"]]), [{"foo":{"bar":{"baz":12}}}]); writeAndCheck([[{"foo/bar":{"op":"set","new":{"baz":12},"ttl":1}}]]); wait(1.1); assertEqual(readAndCheck([["/foo"]]), [{"foo":{}}]); assertEqual(readAndCheck([["/foo/bar"]]), [{"foo":{}}]); assertEqual(readAndCheck([["/foo/bar/baz"]]), [{"foo":{}}]); writeAndCheck([[{"a/u":{"op":"set","new":25, "ttl": 2}}]]); assertEqual(readAndCheck([["/a/u"]]), [{"a":{"u":25}}]); writeAndCheck([[{"a/u":{"op":"set","new":26}}]]); assertEqual(readAndCheck([["/a/u"]]), [{"a":{"u":26}}]); wait(3.0); // key should still be there assertEqual(readAndCheck([["/a/u"]]), [{"a":{"u":26}}]); writeAndCheck([ [{ "/a/u": { "op":"set", "new":{"z":{"z":{"z":"z"}}}, "ttl":30 }}]]); // temporary to make sure we remain with same leader. var tmp = agencyLeader; var leaderErr = false; let res = request({url: agencyLeader + "/_api/agency/stores", method: "GET", followRedirect: true}); if (res.statusCode === 200) { res.bodyParsed = JSON.parse(res.body); if (res.bodyParsed.read_db[0].a !== undefined) { assertTrue(res.bodyParsed.read_db[1]["/a/u"] >= 0); } else { leaderErr = true; // not leader } } else { assertTrue(false); // no point in continuing } // continue ttl test only, if we have not already lost // touch with the leader if (!leaderErr) { writeAndCheck([ [{ "/a/u": { "op":"set", "new":{"z":{"z":{"z":"z"}}} }}]]); res = request({url: agencyLeader + "/_api/agency/stores", method: "GET", followRedirect: true}); // only, if agency is still led by same guy/girl if (agencyLeader === tmp) { if (res.statusCode === 200) { res.bodyParsed = JSON.parse(res.body); console.warn(res.bodyParsed.read_db[0]); if (res.bodyParsed.read_db[0].a !== undefined) { assertTrue(res.bodyParsed.read_db[1]["/a/u"] === undefined); } else { leaderErr = true; } } else { assertTrue(false); // no point in continuing } } else { leaderErr = true; } } if (leaderErr) { require("console").warn("on the record: status code was " + res.statusCode + " couldn't test proper implementation of TTL at this point. not going to startle the chickens over this however and assume rare leader change within."); } }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test "push" operator //////////////////////////////////////////////////////////////////////////////// testOpPush : function () { writeAndCheck([[{"/a/b/c":{"op":"push","new":"max"}}]]); assertEqual(readAndCheck([["/a/b/c"]]), [{a:{b:{c:[1,2,3,"max"]}}}]); writeAndCheck([[{"/a/euler":{"op":"push","new":2.71828182845904523536}}]]); assertEqual(readAndCheck([["/a/euler"]]), [{a:{euler:[2.71828182845904523536]}}]); writeAndCheck([[{"/a/euler":{"op":"set","new":2.71828182845904523536}}]]); assertEqual(readAndCheck([["/a/euler"]]), [{a:{euler:2.71828182845904523536}}]); writeAndCheck([[{"/a/euler":{"op":"push","new":2.71828182845904523536}}]]); assertEqual(readAndCheck([["/a/euler"]]), [{a:{euler:[2.71828182845904523536]}}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test "remove" operator //////////////////////////////////////////////////////////////////////////////// testOpRemove : function () { writeAndCheck([[{"/a/euler":{"op":"delete"}}]]); assertEqual(readAndCheck([["/a/euler"]]), [{a:{}}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test "prepend" operator //////////////////////////////////////////////////////////////////////////////// testOpPrepend : function () { writeAndCheck([[{"/a/b/c":{"op":"prepend","new":3.141592653589793}}]]); assertEqual(readAndCheck([["/a/b/c"]]), [{a:{b:{c:[3.141592653589793,1,2,3,"max"]}}}]); writeAndCheck( [[{"/a/euler":{"op":"prepend","new":2.71828182845904523536}}]]); assertEqual(readAndCheck([["/a/euler"]]), [{a:{euler:[2.71828182845904523536]}}]); writeAndCheck( [[{"/a/euler":{"op":"set","new":2.71828182845904523536}}]]); assertEqual(readAndCheck([["/a/euler"]]), [{a:{euler:2.71828182845904523536}}]); writeAndCheck( [[{"/a/euler":{"op":"prepend","new":2.71828182845904523536}}]]); assertEqual(readAndCheck( [["/a/euler"]]), [{a:{euler:[2.71828182845904523536]}}]); writeAndCheck([[{"/a/euler":{"op":"prepend","new":1.25}}]]); assertEqual(readAndCheck([["/a/euler"]]), [{a:{euler:[1.25,2.71828182845904523536]}}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test "shift" operator //////////////////////////////////////////////////////////////////////////////// testOpShift : function () { writeAndCheck([[{"/a/f":{"op":"shift"}}]]); // none before assertEqual(readAndCheck([["/a/f"]]), [{a:{f:[]}}]); writeAndCheck([[{"/a/e":{"op":"shift"}}]]); // on empty array assertEqual(readAndCheck([["/a/f"]]), [{a:{f:[]}}]); writeAndCheck([[{"/a/b/c":{"op":"shift"}}]]); // on existing array assertEqual(readAndCheck([["/a/b/c"]]), [{a:{b:{c:[1,2,3,"max"]}}}]); writeAndCheck([[{"/a/b/d":{"op":"shift"}}]]); // on existing scalar assertEqual(readAndCheck([["/a/b/d"]]), [{a:{b:{d:[]}}}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test "pop" operator //////////////////////////////////////////////////////////////////////////////// testOpPop : function () { writeAndCheck([[{"/a/f":{"op":"pop"}}]]); // none before assertEqual(readAndCheck([["/a/f"]]), [{a:{f:[]}}]); writeAndCheck([[{"/a/e":{"op":"pop"}}]]); // on empty array assertEqual(readAndCheck([["/a/f"]]), [{a:{f:[]}}]); writeAndCheck([[{"/a/b/c":{"op":"pop"}}]]); // on existing array assertEqual(readAndCheck([["/a/b/c"]]), [{a:{b:{c:[1,2,3]}}}]); writeAndCheck([[{"a/b/d":1}]]); // on existing scalar writeAndCheck([[{"/a/b/d":{"op":"pop"}}]]); // on existing scalar assertEqual(readAndCheck([["/a/b/d"]]), [{a:{b:{d:[]}}}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test "pop" operator //////////////////////////////////////////////////////////////////////////////// testOpErase : function () { writeAndCheck([[{"/version":{"op":"delete"}}]]); writeAndCheck([[{"/a":[0,1,2,3,4,5,6,7,8,9]}]]); // none before assertEqual(readAndCheck([["/a"]]), [{a:[0,1,2,3,4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"erase","val":3}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[0,1,2,4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"erase","val":3}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[0,1,2,4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"erase","val":0}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[1,2,4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"erase","val":1}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[2,4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"erase","val":2}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"erase","val":4}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"erase","val":5}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[6,7,8,9]}]); writeAndCheck([[{"a":{"op":"erase","val":9}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[6,7,8]}]); writeAndCheck([[{"a":{"op":"erase","val":7}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[6,8]}]); writeAndCheck([[{"a":{"op":"erase","val":6}}], [{"a":{"op":"erase","val":8}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[]}]); writeAndCheck([[{"/a":[0,1,2,3,4,5,6,7,8,9]}]]); // none before assertEqual(readAndCheck([["/a"]]), [{a:[0,1,2,3,4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"erase","pos":3}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[0,1,2,4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"erase","pos":0}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[1,2,4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"erase","pos":0}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[2,4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"erase","pos":2}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[2,4,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"erase","pos":4}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[2,4,6,7,9]}]); writeAndCheck([[{"a":{"op":"erase","pos":2}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[2,4,7,9]}]); writeAndCheck([[{"a":{"op":"erase","pos":2}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[2,4,9]}]); writeAndCheck([[{"a":{"op":"erase","pos":0}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[4,9]}]); writeAndCheck([[{"a":{"op":"erase","pos":1}}], [{"a":{"op":"erase","pos":0}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[]}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test "pop" operator //////////////////////////////////////////////////////////////////////////////// testOpReplace : function () { writeAndCheck([[{"/version":{"op":"delete"}}]]); // clear writeAndCheck([[{"/a":[0,1,2,3,4,5,6,7,8,9]}]]); assertEqual(readAndCheck([["/a"]]), [{a:[0,1,2,3,4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"replace","val":3,"new":"three"}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[0,1,2,"three",4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"replace","val":1,"new":[1]}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[0,[1],2,"three",4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"replace","val":[1],"new":[1,2,3]}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[0,[1,2,3],2,"three",4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"replace","val":[1,2,3],"new":[1,2,3]}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[0,[1,2,3],2,"three",4,5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"replace","val":4,"new":[1,2,3]}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[0,[1,2,3],2,"three",[1,2,3],5,6,7,8,9]}]); writeAndCheck([[{"a":{"op":"replace","val":9,"new":[1,2,3]}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[0,[1,2,3],2,"three",[1,2,3],5,6,7,8,[1,2,3]]}]); writeAndCheck([[{"a":{"op":"replace","val":[1,2,3],"new":{"a":0}}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[0,{a:0},2,"three",{a:0},5,6,7,8,{a:0}]}]); writeAndCheck([[{"a":{"op":"replace","val":{"a":0},"new":"a"}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[0,"a",2,"three","a",5,6,7,8,"a"]}]); writeAndCheck([[{"a":{"op":"replace","val":"a","new":"/a"}}]]); assertEqual(readAndCheck([["/a"]]), [{a:[0,"/a",2,"three","/a",5,6,7,8,"/a"]}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test "increment" operator //////////////////////////////////////////////////////////////////////////////// testOpIncrement : function () { writeAndCheck([[{"/version":{"op":"delete"}}]]); writeAndCheck([[{"/version":{"op":"increment"}}]]); // none before assertEqual(readAndCheck([["version"]]), [{version:1}]); writeAndCheck([[{"/version":{"op":"increment"}}]]); // int before assertEqual(readAndCheck([["version"]]), [{version:2}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test "decrement" operator //////////////////////////////////////////////////////////////////////////////// testOpDecrement : function () { writeAndCheck([[{"/version":{"op":"delete"}}]]); writeAndCheck([[{"/version":{"op":"decrement"}}]]); // none before assertEqual(readAndCheck([["version"]]), [{version:-1}]); writeAndCheck([[{"/version":{"op":"decrement"}}]]); // int before assertEqual(readAndCheck([["version"]]), [{version:-2}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test "op" keyword in other places than as operator //////////////////////////////////////////////////////////////////////////////// testOpInStrangePlaces : function () { writeAndCheck([[{"/op":12}]]); assertEqual(readAndCheck([["/op"]]), [{op:12}]); writeAndCheck([[{"/op":{op:"delete"}}]]); writeAndCheck([[{"/op/a/b/c":{"op":"set","new":{"op":13}}}]]); assertEqual(readAndCheck([["/op/a/b/c"]]), [{op:{a:{b:{c:{op:13}}}}}]); writeAndCheck([[{"/op/a/b/c/op":{"op":"increment"}}]]); assertEqual(readAndCheck([["/op/a/b/c"]]), [{op:{a:{b:{c:{op:14}}}}}]); writeAndCheck([[{"/op/a/b/c/op":{"op":"decrement"}}]]); assertEqual(readAndCheck([["/op/a/b/c"]]), [{op:{a:{b:{c:{op:13}}}}}]); writeAndCheck([[{"/op/a/b/c/op":{"op":"pop"}}]]); assertEqual(readAndCheck([["/op/a/b/c"]]), [{op:{a:{b:{c:{op:[]}}}}}]); writeAndCheck([[{"/op/a/b/c/op":{"op":"increment"}}]]); assertEqual(readAndCheck([["/op/a/b/c"]]), [{op:{a:{b:{c:{op:1}}}}}]); writeAndCheck([[{"/op/a/b/c/op":{"op":"shift"}}]]); assertEqual(readAndCheck([["/op/a/b/c"]]), [{op:{a:{b:{c:{op:[]}}}}}]); writeAndCheck([[{"/op/a/b/c/op":{"op":"decrement"}}]]); assertEqual(readAndCheck([["/op/a/b/c"]]), [{op:{a:{b:{c:{op:-1}}}}}]); writeAndCheck([[{"/op/a/b/c/op":{"op":"push","new":-1}}]]); assertEqual(readAndCheck([["/op/a/b/c"]]), [{op:{a:{b:{c:{op:[-1]}}}}}]); writeAndCheck([[{"/op/a/b/d":{"op":"set","new":{"ttl":14}}}]]); assertEqual(readAndCheck([["/op/a/b/d"]]), [{op:{a:{b:{d:{ttl:14}}}}}]); writeAndCheck([[{"/op/a/b/d/ttl":{"op":"increment"}}]]); assertEqual(readAndCheck([["/op/a/b/d"]]), [{op:{a:{b:{d:{ttl:15}}}}}]); writeAndCheck([[{"/op/a/b/d/ttl":{"op":"decrement"}}]]); assertEqual(readAndCheck([["/op/a/b/d"]]), [{op:{a:{b:{d:{ttl:14}}}}}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief op delete on top node //////////////////////////////////////////////////////////////////////////////// testOperatorsOnRootNode : function () { writeAndCheck([[{"/":{"op":"delete"}}]]); assertEqual(readAndCheck([["/"]]), [{}]); writeAndCheck([[{"/":{"op":"increment"}}]]); assertEqual(readAndCheck([["/"]]), [1]); writeAndCheck([[{"/":{"op":"delete"}}]]); writeAndCheck([[{"/":{"op":"decrement"}}]]); assertEqual(readAndCheck([["/"]]), [-1]); writeAndCheck([[{"/":{"op":"push","new":"Hello"}}]]); assertEqual(readAndCheck([["/"]]), [["Hello"]]); writeAndCheck([[{"/":{"op":"delete"}}]]); writeAndCheck([[{"/":{"op":"push","new":"Hello"}}]]); assertEqual(readAndCheck([["/"]]), [["Hello"]]); writeAndCheck([[{"/":{"op":"pop"}}]]); assertEqual(readAndCheck([["/"]]), [[]]); writeAndCheck([[{"/":{"op":"pop"}}]]); assertEqual(readAndCheck([["/"]]), [[]]); writeAndCheck([[{"/":{"op":"push","new":"Hello"}}]]); assertEqual(readAndCheck([["/"]]), [["Hello"]]); writeAndCheck([[{"/":{"op":"shift"}}]]); assertEqual(readAndCheck([["/"]]), [[]]); writeAndCheck([[{"/":{"op":"shift"}}]]); assertEqual(readAndCheck([["/"]]), [[]]); writeAndCheck([[{"/":{"op":"prepend","new":"Hello"}}]]); assertEqual(readAndCheck([["/"]]), [["Hello"]]); writeAndCheck([[{"/":{"op":"shift"}}]]); assertEqual(readAndCheck([["/"]]), [[]]); writeAndCheck([[{"/":{"op":"pop"}}]]); assertEqual(readAndCheck([["/"]]), [[]]); writeAndCheck([[{"/":{"op":"delete"}}]]); assertEqual(readAndCheck([["/"]]), [{}]); writeAndCheck([[{"/":{"op":"delete"}}]]); assertEqual(readAndCheck([["/"]]), [{}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test observe / unobserve //////////////////////////////////////////////////////////////////////////////// testObserve : function () { var res, before, after, clean; var trx = [{"/a":"a"}, {"a":{"oldEmpty":true}}]; // In the beginning res = request({url:agencyLeader+"/_api/agency/stores", method:"GET"}); assertEqual(200, res.statusCode); clean = JSON.parse(res.body); // Don't create empty object for observation writeAndCheck([[{"/a":{"op":"observe", "url":"https://google.com"}}]]); assertEqual(readAndCheck([["/"]]), [{}]); res = accessAgency("write",[trx]); assertEqual(res.statusCode, 200); res = accessAgency("write",[trx]); assertEqual(res.statusCode, 412); writeAndCheck([[{"/":{"op":"delete"}}]]); var c = agencyConfig().term; // No duplicate entries in res = request({url:agencyLeader+"/_api/agency/stores", method:"GET"}); assertEqual(200, res.statusCode); before = JSON.parse(res.body); writeAndCheck([[{"/a":{"op":"observe", "url":"https://google.com"}}]]); res = request({url:agencyLeader+"/_api/agency/stores", method:"GET"}); assertEqual(200, res.statusCode); after = JSON.parse(res.body); if (!_.isEqual(before, after)) { if (agencyConfig().term === c) { assertEqual(before, after); //peng } else { require("console").warn("skipping remaining callback tests this time around"); return; // } } // Normalization res = request({url:agencyLeader+"/_api/agency/stores", method:"GET"}); assertEqual(200, res.statusCode); before = JSON.parse(res.body); writeAndCheck([[{"//////a////":{"op":"observe", "url":"https://google.com"}}]]); writeAndCheck([[{"a":{"op":"observe", "url":"https://google.com"}}]]); writeAndCheck([[{"a/":{"op":"observe", "url":"https://google.com"}}]]); writeAndCheck([[{"/a/":{"op":"observe", "url":"https://google.com"}}]]); res = request({url:agencyLeader+"/_api/agency/stores", method:"GET"}); assertEqual(200, res.statusCode); after = JSON.parse(res.body); if (!_.isEqual(before, after)) { if (agencyConfig().term === c) { assertEqual(before, after); //peng } else { require("console").warn("skipping remaining callback tests this time around"); return; // } } // Unobserve res = request({url:agencyLeader+"/_api/agency/stores", method:"GET"}); assertEqual(200, res.statusCode); before = JSON.parse(res.body); writeAndCheck([[{"//////a":{"op":"unobserve", "url":"https://google.com"}}]]); res = request({url:agencyLeader+"/_api/agency/stores", method:"GET"}); assertEqual(200, res.statusCode); after = JSON.parse(res.body); assertEqual(clean, after); if (!_.isEqual(clean, after)) { if (agencyConfig().term === c) { assertEqual(clean, after); //peng } else { require("console").warn("skipping remaining callback tests this time around"); return; // } } writeAndCheck([[{"/":{"op":"delete"}}]]); assertEqual(readAndCheck([["/"]]), [{}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test delete / replace / erase should not create new stuff in agency //////////////////////////////////////////////////////////////////////////////// testNotCreate : function () { var trx = [{"/a":"a"}, {"a":{"oldEmpty":true}}], res; // Don't create empty object for observation writeAndCheck([[{"a":{"op":"delete"}}]]); assertEqual(readAndCheck([["/"]]), [{}]); res = accessAgency("write",[trx]); assertEqual(res.statusCode, 200); res = accessAgency("write",[trx]); assertEqual(res.statusCode, 412); writeAndCheck([[{"/":{"op":"delete"}}]]); assertEqual(readAndCheck([["/"]]), [{}]); // Don't create empty object for observation writeAndCheck([[{"a":{"op":"replace", "val":1, "new":2}}]]); assertEqual(readAndCheck([["/"]]), [{}]); res = accessAgency("write",[trx]); assertEqual(res.statusCode, 200); res = accessAgency("write",[trx]); assertEqual(res.statusCode, 412); writeAndCheck([[{"/":{"op":"delete"}}]]); assertEqual(readAndCheck([["/"]]), [{}]); // Don't create empty object for observation writeAndCheck([[{"a":{"op":"erase", "val":1}}]]); assertEqual(readAndCheck([["/"]]), [{}]); res = accessAgency("write",[trx]); assertEqual(res.statusCode, 200); res = accessAgency("write",[trx]); assertEqual(res.statusCode, 412); writeAndCheck([[{"/":{"op":"delete"}}]]); assertEqual(readAndCheck([["/"]]), [{}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test that order should not matter //////////////////////////////////////////////////////////////////////////////// testOrder : function () { writeAndCheck([[{"a":{"b":{"c":[1,2,3]},"e":12},"d":false}]]); assertEqual(readAndCheck([["a/e"],[ "d","a/b"]]), [{a:{e:12}},{a:{b:{c:[1,2,3]},d:false}}]); writeAndCheck([[{"/":{"op":"delete"}}]]); writeAndCheck([[{"d":false, "a":{"b":{"c":[1,2,3]},"e":12}}]]); assertEqual(readAndCheck([["a/e"],[ "d","a/b"]]), [{a:{e:12}},{a:{b:{c:[1,2,3]},d:false}}]); writeAndCheck([[{"d":false, "a":{"e":12,"b":{"c":[1,2,3]}}}]]); assertEqual(readAndCheck([["a/e"],[ "d","a/b"]]), [{a:{e:12}},{a:{b:{c:[1,2,3]},d:false}}]); writeAndCheck([[{"d":false, "a":{"e":12,"b":{"c":[1,2,3]}}}]]); assertEqual(readAndCheck([["a/e"],["a/b","d"]]), [{a:{e:12}},{a:{b:{c:[1,2,3]},d:false}}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test nasty willful attempt to break //////////////////////////////////////////////////////////////////////////////// testOrderEvil : function () { writeAndCheck([[{"a":{"b":{"c":[1,2,3]},"e":12},"d":false}]]); assertEqual(readAndCheck([["a/e"],[ "d","a/b"]]), [{a:{e:12}},{a:{b:{c:[1,2,3]},d:false}}]); writeAndCheck([[{"/":{"op":"delete"}}]]); writeAndCheck([[{"d":false, "a":{"b":{"c":[1,2,3]},"e":12}}]]); assertEqual(readAndCheck([["a/e"],[ "d","a/b"]]), [{a:{e:12}},{a:{b:{c:[1,2,3]},d:false}}]); writeAndCheck([[{"d":false, "a":{"e":12,"b":{"c":[1,2,3]}}}]]); assertEqual(readAndCheck([["a/e"],[ "d","a/b"]]), [{a:{e:12}},{a:{b:{c:[1,2,3]},d:false}}]); writeAndCheck([[{"d":false, "a":{"e":12,"b":{"c":[1,2,3]}}}]]); assertEqual(readAndCheck([["a/e"],["a/b","d"]]), [{a:{e:12}},{a:{b:{c:[1,2,3]},d:false}}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test nasty willful attempt to break //////////////////////////////////////////////////////////////////////////////// testSlashORama : function () { writeAndCheck([[{"/":{"op":"delete"}}]]); writeAndCheck([[{"//////////////////////a/////////////////////b//": {"b///////c":4}}]]); assertEqual(readAndCheck([["/"]]), [{a:{b:{b:{c:4}}}}]); writeAndCheck([[{"/":{"op":"delete"}}]]); writeAndCheck([[{"////////////////////////": "Hi there!"}]]); assertEqual(readAndCheck([["/"]]), ["Hi there!"]); writeAndCheck([[{"/":{"op":"delete"}}]]); writeAndCheck( [[{"/////////////////\\/////a/////////////^&%^&$^&%$////////b\\\n//": {"b///////c":4}}]]); assertEqual(readAndCheck([["/"]]), [{"\\":{"a":{"^&%^&$^&%$":{"b\\\n":{"b":{"c":4}}}}}}]); }, testKeysBeginningWithSameString: function() { var res = accessAgency("write",[[{"/bumms":{"op":"set","new":"fallera"}, "/bummsfallera": {"op":"set","new":"lalalala"}}]]); assertEqual(res.statusCode, 200); assertEqual(readAndCheck([["/bumms", "/bummsfallera"]]), [{bumms:"fallera", bummsfallera: "lalalala"}]); }, testHiddenAgencyWrite: function() { var res = accessAgency("write",[[{".agency": {"op":"set","new":"fallera"}}]]); assertEqual(res.statusCode, 403); }, testHiddenAgencyWriteSlash: function() { var res = accessAgency("write",[[{"/.agency": {"op":"set","new":"fallera"}}]]); assertEqual(res.statusCode, 403); }, testHiddenAgencyWriteDeep: function() { var res = accessAgency("write",[[{"/.agency/hans": {"op":"set","new":"fallera"}}]]); assertEqual(res.statusCode, 403); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Compaction //////////////////////////////////////////////////////////////////////////////// testLogCompaction: function() { // Find current log index and erase all data: let cur = accessAgency("write",[[{"/": {"op":"delete"}}]]). bodyParsed.results[0]; let count = compactionConfig.compactionStepSize - 100 - cur; require("console").topic("agency=info", "Avoiding log compaction for now with", count, "keys, from log entry", cur, "on."); doCountTransactions(count, 0); // Now trigger one log compaction and check all keys: let count2 = compactionConfig.compactionStepSize + 100 - (cur + count); require("console").topic("agency=info", "Provoking log compaction for now with", count2, "keys, from log entry", cur + count, "on."); doCountTransactions(count2, count); // All tests so far have not really written many log entries in // comparison to the compaction interval (with the default settings), let count3 = 2 * compactionConfig.compactionStepSize + 100 - (cur + count + count2); require("console").topic("agency=info", "Provoking second log compaction for now with", count3, "keys, from log entry", cur + count + count2, "on."); doCountTransactions(count3, count + count2); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Huge transaction package //////////////////////////////////////////////////////////////////////////////// testHugeTransactionPackage : function() { writeAndCheck([[{"a":{"op":"delete"}}]]); // cleanup first var huge = []; for (var i = 0; i < 20000; ++i) { huge.push([{"a":{"op":"increment"}}, {}, "huge" + i]); } writeAndCheck(huge, 600); assertEqual(readAndCheck([["a"]]), [{"a":20000}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Huge transaction package, inc/dec //////////////////////////////////////////////////////////////////////////////// testTransactionWithIncDec : function() { writeAndCheck([[{"a":{"op":"delete"}}]]); // cleanup first var trx = []; for (var i = 0; i < 100; ++i) { trx.push([{"a":{"op":"increment"}}, {}, "inc" + i]); trx.push([{"a":{"op":"decrement"}}, {}, "dec" + i]); } writeAndCheck(trx); assertEqual(readAndCheck([["a"]]), [{"a":0}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Transaction, update of same key //////////////////////////////////////////////////////////////////////////////// testTransactionUpdateSameKey : function() { writeAndCheck([[{"a":{"op":"delete"}}]]); // cleanup first var trx = []; trx.push([{"a":"foo"}]); trx.push([{"a":"bar"}]); writeAndCheck(trx); assertEqual(readAndCheck([["a"]]), [{"a":"bar"}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Transaction, insert and remove of same key //////////////////////////////////////////////////////////////////////////////// testTransactionInsertRemoveSameKey : function() { writeAndCheck([[{"a":{"op":"delete"}}]]); // cleanup first var trx = []; trx.push([{"a":"foo"}]); trx.push([{"a":{"op":"delete"}}]); writeAndCheck(trx); assertEqual(readAndCheck([["/a"]]), [{}]); }, //////////////////////////////////////////////////////////////////////////////// /// @brief Huge transaction package, all different keys //////////////////////////////////////////////////////////////////////////////// testTransactionDifferentKeys : function() { writeAndCheck([[{"a":{"op":"delete"}}]]); // cleanup first var huge = [], i; for (i = 0; i < 100; ++i) { huge.push([{["a" + i]:{"op":"increment"}}, {}, "diff" + i]); } writeAndCheck(huge); for (i = 0; i < 100; ++i) { assertEqual(readAndCheck([["a" + i]]), [{["a" + i]:1}]); } }, //////////////////////////////////////////////////////////////////////////////// /// @brief Test compaction step/keep //////////////////////////////////////////////////////////////////////////////// // Test currently deactivated, it at the very least takes very long, // it might be broken in its entirety. /*testCompactionStepKeep : function() { // prepare transaction package for tests var transaction = [], i; for (i = 0; i < compactionConfig.compactionStepSize; i++) { transaction.push([{"foobar":{"op":"increment"}}]); } writeAndCheck([[{"/":{"op":"delete"}}]]); // cleanup first writeAndCheck([[{"foobar":0}]]); // cleanup first var foobar = accessAgency("read", [["foobar"]]).bodyParsed[0].foobar; var llogi = evalComp(); assertTrue(llogi > 0); // at this limit we should see keep size to kick in var lim = compactionConfig.compactionKeepSize - llogi; // 1st package writeAndCheck(transaction); lim -= transaction.length; assertTrue(evalComp()>0); writeAndCheck(transaction); lim -= transaction.length; assertTrue(evalComp()>0); while(lim > compactionConfig.compactionStepSize) { writeAndCheck(transaction); lim -= transaction.length; } assertTrue(evalComp()>0); writeAndCheck(transaction); assertTrue(evalComp()>0); writeAndCheck(transaction); assertTrue(evalComp()>0); writeAndCheck(transaction); assertTrue(evalComp()>0); } */ }; } //////////////////////////////////////////////////////////////////////////////// /// @brief executes the test suite //////////////////////////////////////////////////////////////////////////////// jsunity.run(agencyTestSuite); return jsunity.done();