-
Notifications
You must be signed in to change notification settings - Fork 2
/
tests.js
269 lines (260 loc) · 12 KB
/
tests.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
const test=require('node:test'), assert=require('node:assert'), fs=require('node:fs');
const {serve, connect, sync, desync, objToString, stringToObj, partFilter, objValueFrom, setConsistency}=require('.');
(async function(){
//you would notice a lot of ",null,null,false" in each "connect"
//this is to set "onFail" to false, to disable automatic reconnecting
let slash=process.platform=="win32"?"\\":"/", serverLocation="ws://localhost:8009"
let log=(text)=>console.log('\x1b[1m\x1b[33m'+text+'\x1b[0m')
const mainObj={a:{a1:1,a2:{a3:0}},b:{b1:2},c:new Uint8Array([3,4]),undefined};
mainObj.m=mainObj;
const mainObj2={n:1,i:true,o:false,p:true};
const myWebject=serve(mainObj)
let lvl3Key=myWebject.addToken(3)
let lvl2Key=myWebject.addToken(2)
let viewKey=myWebject.addToken(1), viewKey2=myWebject.addToken(1,mainObj2)
let manualToken=myWebject.addToken(1,mainObj2,"manual_token")
const sharedObj=await connect(serverLocation,viewKey,null,null,false)
const sharedObj1=await connect(serverLocation,viewKey,null,null,false)
const sharedObj2=await connect(serverLocation,viewKey2,null,null,false)
async function sharedObjectsEqual(){
await new Promise(ok=>setTimeout(ok,50)) //by now change should be implemented
for(let i=0;i<arguments.length;i++)
assert.deepStrictEqual(mainObj,arguments[i]);
}
//first set of tests
await test("1) Ensuring Objects Shared",async function(t){
await t.test("After initial 'connect'",async function(){
await sharedObjectsEqual(sharedObj,sharedObj1)
})
await t.test("On modification",async function(){
mainObj.c[0]++;
await sharedObjectsEqual(sharedObj,sharedObj1)
})
await t.test("On deletion",async function(){
delete mainObj.m;
await sharedObjectsEqual(sharedObj,sharedObj1)
})
await t.test("On insertion",async function(){
mainObj.m=mainObj;
await sharedObjectsEqual(sharedObj,sharedObj1)
})
await t.test("Do functions get ignored?",async function(){
mainObj.fn=function(){return 'boilerplate'}
await new Promise(ok=>setTimeout(ok,50)) //by now change should be implemented
assert.strictEqual(Object.getOwnPropertyDescriptor(sharedObj,'fn'),undefined) //key was not shared
delete mainObj.fn
})
})
//second set of tests
await test("2) Serialisation Based Checks",async function(t){
await t.test("Conversion between objToString and stringToObj",async function(){
assert.deepStrictEqual(stringToObj(objToString(mainObj,true)),mainObj)
})
await t.test("Usage of objValueFrom",async function(){
assert.strictEqual(objValueFrom(['c','0'],mainObj),mainObj.c[0])
assert.strictEqual(objValueFrom(['a','a2','a3'],mainObj),mainObj.a.a2.a3)
})
await t.test("Ensuring Correct Filters Generated from partFilter",async function(){
const testObj={a:{b:{c:2}}}, testObjClone={a:{b:{c:2}}}, testObj1={a:{b:{c:2}}}
objToString(testObj)
objToString(testObj1)
testObj1.a.b.c++;
testObj1.d="e";
testObj1.a.b.f=testObj1;
testObj1.a.b.f.g=2225;
const edits=objToString(testObj1)
stringToObj(edits,testObj,partFilter(["a","b"])) //no edits should've occured
assert.deepStrictEqual(testObj,testObjClone)
stringToObj(edits,testObj,partFilter(["a","b"],true)) //only c should've been edited
assert.strictEqual(testObj.d,undefined)
assert.strictEqual(testObj.a.b.f,undefined)
assert.strictEqual(testObj.a.b.c,3)
})
await t.test("Serialisation Crash Testing",async function(){
const date=new Date(), obj={events:[{date,arr:['value1']}]}
const clone=stringToObj(objToString(obj)), {events}=obj
const standard={"events":[{"date":date-0,"arr":["value1","value2","value2","value2","value2","value2"]}]}
let update=_=>stringToObj(objToString(obj),clone);
for(let i=0;i<5;i++){
obj.events[0].date-=0;
update()
obj.events=[{date}];
update()
obj.events[0].date-=0;
update()
obj.events=null;
update()
events[0].arr.push("value"+(events.length+1));
obj.events=events;
update() //the call that crashes it
}
assert.deepStrictEqual(obj,clone)
assert.deepStrictEqual(obj,standard)
})
})
//third set of tests
await test("3) Verification of 'webject' Methods",async function(t){
var count=0
function lockListener({lock}){ //destructuring example(function still works)
//this function is used in addListener
count+=5
lock() //the token used to connect with was locked
}
await t.test("Manual authToken connection",async function(){
let resolve=null, temp=new Promise(r=>resolve=r), resolver=ev=>resolve(ev.token.authToken);
myWebject.addListener("connect",resolver)
assert.deepStrictEqual(await connect(serverLocation,manualToken,null,null,false),sharedObj2)
assert.strictEqual(await temp,manualToken)
myWebject.endListener("connect",resolver)
})
await t.test("Locking and unlocking",async function(){
myWebject.addListener("connect",lockListener)
assert.deepStrictEqual(await connect(serverLocation,manualToken,null,null,false),sharedObj2)
try{
await connect(serverLocation,manualToken,null,null,false)
throw new Error("Connection did not throw; locking failed")
}
catch(err){
assert.strictEqual(
(err.message||err).split('\n').at(-1),
"authToken LOCKED: this is a correct key, but it takes no new connections 0_0"
)
//"magic string" source: webject.js "disconnectHandle" function inside "connect" module
}
})
await t.test("Proof of endListener",async function(){
let currentCount=count
myWebject.endListener("connect",lockListener) //count changes every connection
assert.deepStrictEqual(await connect(serverLocation,viewKey,null,null,false),mainObj) //but it won't change
assert.deepStrictEqual(await connect(serverLocation,viewKey,null,null,false),mainObj) //didn't throw locked error
assert.strictEqual(currentCount,count) //no change since listener was already ended
})
await t.test("Ensuring endToken works",async function(){
myWebject.endToken(manualToken)
try{
await connect(serverLocation,manualToken,null,null,false)
throw new Error("Connection did not throw; endToken failed")
}
catch(err){
assert.strictEqual(
(err.message||err).split('\n').at(-1),
"closed PURPOSEFULLY: check your location and token parameters, OR you got BOOTED :/"
)
//"magic string" source: webject.js "disconnectHandle" function inside "connect" module
}
})
//added back the test to ensure it passes but commented out for when pushing to the repo
/*await t.test("Ensuring ping logic doesn't disconnect you",async function(){
await new Promise(r=>setTimeout(r,71001))
mainObj.newkeyy=3;
await sharedObjectsEqual(sharedObj,sharedObj1)
})*/
await t.test("authLevel 2 and 3 tokens",async function(){
let temp2=await connect(serverLocation,lvl2Key,null,null,false) //can only insert new items
let temp3=await connect(serverLocation,lvl3Key,null,null,false) //can delete, modify, insert new items
delete temp3.newkeyy;
await sharedObjectsEqual(sharedObj,sharedObj1,temp3)
temp2.newkeyy=4;
await sharedObjectsEqual(sharedObj,sharedObj1,temp2)
temp2.newkeyy++;
await sharedObjectsEqual(sharedObj,sharedObj1)
assert.notDeepStrictEqual(mainObj,temp2) //since lvl2 shouldn't be able to modify
delete temp2.newkeyy;
await sharedObjectsEqual(sharedObj,sharedObj1)
assert.notDeepStrictEqual(mainObj,temp2) //since lvl2 shouldn't be able to delete
})
//the issue is that everyone gets the edit
await t.test("Edit and disconnect handling",async function(){
let resolve=null, temp=new Promise(r=>resolve=r)
function resolver({token}){resolve(token.authToken)}
function editHandler({socket,token}){
if(token.authToken===viewKey){
assert.ok(!token.clients.get(socket)) //socket was NOT from a viewKey token
assert.strictEqual(socket.token.authToken,lvl3Key) //it was in fact from lvl3Key token
//the socket argument is from WHO EDITED, if null, edit originated on server side
socket?.close(1000); //temp3 closed
}
assert.strictEqual(token.object,mainObj) //every edit event is with a token that has mainObj
}
myWebject.addListener("edit",editHandler)
myWebject.addListener("disconnect",resolver)
let temp3=await connect(serverLocation,lvl3Key,null,null,false) //can delete, modify, insert new items
temp3.newKey=2 //an edit
assert.strictEqual(await temp,lvl3Key)
temp3.newKey++ //second edit where temp3 is already disconnected
await sharedObjectsEqual(sharedObj,sharedObj1)
assert.notDeepStrictEqual(temp3,mainObj)
myWebject.endListener("edit",editHandler)
myWebject.endListener("disconnect",resolver)
})
})
//fourth set of tests
await test("4) Verification of 'connect' handling of 'onFail'",async function(t){
let mySharedObj={}, count=5, resolve=null, p=new Promise(r=>resolve=r);
function rejector(ev){
log('connection made.. disconnecting...\n')
setTimeout(()=>ev.socket.close(),10)
}
myWebject.addListener("connect",rejector)
async function loop(){
if(--count<1) resolve(); //stop execution
await new Promise(r=>setTimeout(r,300)) //so it doesn't try to re-connect instantly
return await connect(serverLocation,lvl3Key,mySharedObj,null,loop)
//remember that the default port it tries to put the websocket on is 8009 when you don't give it a server
}
loop()
await p
assert.deepStrictEqual(mySharedObj,mainObj)
myWebject.endListener("connect",rejector)
})
//fifth set of tests
await test("5) Usage of 'sync', 'desync' and 'setConsistency' Functions",async function(t){
const filePath=__dirname+slash+"record"
await t.test("Ensuring sync works",async function(){
sync(filePath,mainObj)
for(let i=0;i<5;i++){
mainObj.c[0]++;
await new Promise(r=>setTimeout(r,50))
assert.deepStrictEqual(stringToObj(fs.readFileSync(filePath+'.json')), mainObj) //file updated
}
})
await t.test("Ensuring consistency works correctly with sync",async function(){
mainObj.c[0]++;
setConsistency(mainObj,false)
await new Promise(r=>setTimeout(r,50))
assert.notDeepStrictEqual(stringToObj(fs.readFileSync(filePath+'.json')), mainObj) //consistency false
mainObj.c[0]++;
setConsistency(mainObj,true)
await new Promise(r=>setTimeout(r,50))
assert.deepStrictEqual(stringToObj(fs.readFileSync(filePath+'.json')), mainObj) //consistency true
})
await t.test("Ensuring desync works",async function(){
desync(filePath)
mainObj.c[0]++;
await new Promise(r=>setTimeout(r,50))
assert.notDeepStrictEqual(stringToObj(fs.readFileSync(filePath+'.json')), mainObj) //file not updated
fs.unlinkSync(filePath+'.json')
})
})
//sixth set of tests
await test("6) Usage of Encoding {encoder,decoder}",async function(t){
//these following functions r async just to make sure the encoding functions are awaited on
async function encoder(data){
await new Promise(r=>setTimeout(r,2))
return btoa(data)
}
async function decoder(data){
await new Promise(r=>setTimeout(r,2))
return atob(data)
}
const filePath=__dirname+slash+"record"
const encodingKey=myWebject.addToken(1,0,0,{encoder,decoder})
//the 0s for falsish values to invoke their default values instead
const mySharedObj=await connect(serverLocation,encodingKey,null,{encoder,decoder},false)
sync(filePath,mySharedObj,{encoder,decoder})
await new Promise(r=>setTimeout(r,50))
assert.strictEqual(await encoder(objToString(mainObj,true)),fs.readFileSync(filePath+'.json').toString())
fs.unlinkSync(filePath+'.json')
})
setTimeout(_=>process.exit(0),50)
})()