@live-change/pattern-db 0.2.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +11 -0
- package/lib/relations-store.js +74 -43
- package/package.json +5 -4
- package/tests/chain-with-expire.test.js +143 -0
- package/tests/linked-chain-with-expire.test.js +145 -0
- package/tests/linked-simple-chain.test.js +124 -0
- package/tests/simple-chain.test.js +306 -0
- package/tests/svg.js +115 -0
- package/tests/testDb.js +60 -0
- package/tests/viral-loop.test.js +413 -0
- package/.github/workflows/npmpublish.yml +0 -31
package/LICENSE.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Copyright 2019-2024 Michał Łaszczewski
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
4
|
+
|
|
5
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
6
|
+
|
|
7
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
8
|
+
|
|
9
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
10
|
+
|
|
11
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/lib/relations-store.js
CHANGED
|
@@ -1,56 +1,80 @@
|
|
|
1
1
|
const lcp = require("@live-change/pattern")
|
|
2
|
+
const crypto = require("crypto")
|
|
2
3
|
|
|
3
4
|
function relationsStore(dao, database, table) {
|
|
4
5
|
|
|
5
6
|
async function createTable() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
7
|
+
console.log("CREATE TABLE", table)
|
|
8
|
+
try {
|
|
9
|
+
await dao.request(['database', 'createTable'], database, table)
|
|
10
|
+
} catch(e) {
|
|
11
|
+
// console.error(e)
|
|
12
|
+
}
|
|
13
|
+
console.log("CREATE INDEX", table+'_eventTypeAndKeys')
|
|
14
|
+
try {
|
|
15
|
+
await dao.request(['database', 'createIndex'], database, table+'_eventTypeAndKeys', `(${
|
|
16
|
+
async function(input, output, { table }) {
|
|
17
|
+
const mapper = (obj) => ({ id: obj.eventType+'_'+obj.keys+'_'+obj.id, to: obj.id })
|
|
18
|
+
await input.table(table).onChange((obj, oldObj) =>
|
|
19
|
+
output.change(obj && mapper(obj), oldObj && mapper(oldObj)) )
|
|
20
|
+
}
|
|
21
|
+
})`, { table })
|
|
22
|
+
} catch(e) {
|
|
23
|
+
// console.error(e)
|
|
24
|
+
}
|
|
25
|
+
console.log("CREATE INDEX", table+'_sourceRelation')
|
|
26
|
+
try {
|
|
27
|
+
await dao.request(['database', 'createIndex'], database, table+'_sourceRelation', `(${
|
|
28
|
+
async function(input, output, { table }) {
|
|
29
|
+
const mapper = (obj) => ({ id: obj.source+'_'+obj.relation+'_'+obj.id, to: obj.id })
|
|
30
|
+
await input.table(table).onChange((obj, oldObj) =>
|
|
31
|
+
output.change(obj && mapper(obj), oldObj && mapper(oldObj)) )
|
|
32
|
+
}
|
|
33
|
+
})`, { table })
|
|
34
|
+
} catch(e) {
|
|
35
|
+
// console.error(e)
|
|
36
|
+
}
|
|
21
37
|
}
|
|
22
38
|
|
|
23
39
|
async function getRelations(type, keys) {
|
|
40
|
+
console.log("GET RELATIONS", type, keys)
|
|
24
41
|
let keysList = Object.keys(keys).map(k => [k, keys[k]]).filter(([a,b]) => !!b)
|
|
25
42
|
keysList.sort((a,b) => a[0] == b[0] ? 0 : (a[0] > b[0] ? 1 : -1))
|
|
26
43
|
let keySets = lcp.allCombinations(keysList)
|
|
27
|
-
|
|
44
|
+
const promises = new Array(keySets.length)
|
|
28
45
|
for(let i = 0; i < keySets.length; i++) {
|
|
29
46
|
const ks = keySets[i]
|
|
30
|
-
|
|
47
|
+
// console.log("CHECKING KEYSET", ks)
|
|
48
|
+
// const tableData = await dao.get(['database', 'tableRange', database, table, {}])
|
|
49
|
+
// const indexData = await dao.get(['database', 'indexRange', database, table+'_eventTypeAndKeys', {}])
|
|
50
|
+
// console.log("IN TABLE:", tableData)
|
|
51
|
+
// console.log("IN INDEX:", indexData)
|
|
52
|
+
promises[i] = dao.get(['database', 'query', database, `(${
|
|
31
53
|
async (input, output, { table, type, ks }) => {
|
|
32
54
|
const mapper = async (res) => input.table(table).object(res.to).get()
|
|
33
55
|
await input.index(table + "_eventTypeAndKeys").range({
|
|
34
56
|
gte: type + '_' + ks + '_',
|
|
35
57
|
lte: type + '_' + ks + "_\xFF\xFF\xFF\xFF"
|
|
36
58
|
}).onChange(async (obj, oldObj) => {
|
|
37
|
-
output.change(obj && await mapper(obj), oldObj && await mapper(oldObj))
|
|
59
|
+
await output.change(obj && await mapper(obj), oldObj && await mapper(oldObj))
|
|
38
60
|
})
|
|
39
61
|
}
|
|
40
62
|
})`, { table, type, ks}])
|
|
41
|
-
|
|
42
|
-
promises[i] = dao.get(['database', 'indexRange', database, table+'_eventTypeAndKeys', {
|
|
43
|
-
gte: type+'_'+ks+'_',
|
|
44
|
-
lte: type+'_'+ks+"_\xFF\xFF\xFF\xFF"
|
|
45
|
-
}])
|
|
46
63
|
}
|
|
47
|
-
const
|
|
48
|
-
|
|
64
|
+
const queryResults = await Promise.all(promises)
|
|
65
|
+
const results = queryResults.flat().map(res => {
|
|
66
|
+
const keys = {}
|
|
67
|
+
for(let kd of res.keys) {
|
|
68
|
+
keys[kd[0]] = kd[1]
|
|
69
|
+
}
|
|
70
|
+
return { ...res, keys }
|
|
71
|
+
})
|
|
72
|
+
return results
|
|
49
73
|
}
|
|
50
74
|
|
|
51
75
|
const relationOperations = new Map() // need to queue operations on keys
|
|
52
76
|
|
|
53
|
-
function queueRelationChange(type, keysList, relationId, changeFun) {
|
|
77
|
+
async function queueRelationChange(type, keysList, relationId, changeFun) {
|
|
54
78
|
const rKey = JSON.stringify([type, keysList, relationId])
|
|
55
79
|
let op = relationOperations.get(rKey)
|
|
56
80
|
return new Promise(async (resolve, reject) => {
|
|
@@ -75,15 +99,19 @@ function relationsStore(dao, database, table) {
|
|
|
75
99
|
output.change(obj && await mapper(obj), oldObj && await mapper(oldObj))
|
|
76
100
|
})
|
|
77
101
|
}
|
|
78
|
-
})`, { table, type, ks}])
|
|
79
|
-
let
|
|
102
|
+
})`, { table, type, ks: keysList}])
|
|
103
|
+
let foundRelation = relations.find(rel => rel.relation == relationId)
|
|
104
|
+
//console.log("FOUND RELATION", foundRelation)
|
|
105
|
+
let currentRelation = foundRelation
|
|
80
106
|
while (op.changes.length > 0) {
|
|
81
107
|
for (const change of op.changes) currentRelation = change(currentRelation)
|
|
82
108
|
op.changes = []
|
|
109
|
+
//console.log("CHANGED RELATION", currentRelation)
|
|
83
110
|
if (currentRelation) {
|
|
111
|
+
if(!currentRelation.id) currentRelation.id = crypto.randomBytes(16).toString("hex")
|
|
112
|
+
console.log("PUT RELATION", currentRelation)
|
|
84
113
|
await dao.request(['database', 'put', database, table, currentRelation])
|
|
85
|
-
|
|
86
|
-
await dao.request(['database', 'delete', database, table, currentRelation.id])
|
|
114
|
+
//console.log("RELATION WRITTEN!")
|
|
87
115
|
}
|
|
88
116
|
}
|
|
89
117
|
relationOperations.delete(rKey)
|
|
@@ -99,26 +127,29 @@ function relationsStore(dao, database, table) {
|
|
|
99
127
|
let promises = []
|
|
100
128
|
let keysList = Object.keys(relation.keys).map(k => [k, relation.keys[k]]).filter(([a,b]) => !!b)
|
|
101
129
|
keysList.sort((a,b) => a[0] == b[0] ? 0 : (a[0] > b[0] ? 1 : -1))
|
|
130
|
+
//console.log("RELATION SAVE!")
|
|
102
131
|
for(const type of relation.eventTypes) {
|
|
103
132
|
promises.push(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
133
|
+
queueRelationChange(type, keysList, relation.relation, (currentRelation) => {
|
|
134
|
+
if(currentRelation) {
|
|
135
|
+
currentRelation.prev.push(...relation.prev)
|
|
136
|
+
if(mark) mark(currentRelation)
|
|
137
|
+
return currentRelation
|
|
138
|
+
} else {
|
|
139
|
+
const r = { ...relation, eventType: type, keys: keysList }
|
|
140
|
+
if(mark) mark(r)
|
|
141
|
+
return r
|
|
142
|
+
}
|
|
143
|
+
})
|
|
115
144
|
)
|
|
116
145
|
}
|
|
117
146
|
await Promise.all(promises)
|
|
147
|
+
//console.log("RELATION SAVED!")
|
|
118
148
|
}
|
|
119
149
|
|
|
120
150
|
async function removeRelation(relation) {
|
|
121
|
-
|
|
151
|
+
//console.log("REMOVE RELATION", relation)
|
|
152
|
+
return dao.request(['database', 'query'], database, `(${
|
|
122
153
|
async (input, output, { relation, table }) =>
|
|
123
154
|
await input.index(table+"_sourceRelation").range({
|
|
124
155
|
gte: relation.source+'_'+relation.relation+'_',
|
|
@@ -126,7 +157,7 @@ function relationsStore(dao, database, table) {
|
|
|
126
157
|
}).onChange((obj, oldObj) => {
|
|
127
158
|
output.table(table).delete(obj.to)
|
|
128
159
|
})
|
|
129
|
-
})`, { table, relation }
|
|
160
|
+
})`, { table, relation })
|
|
130
161
|
}
|
|
131
162
|
|
|
132
163
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/pattern-db",
|
|
3
|
-
"version": "0.2
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Realtime usage pattern analysis - database storage backend",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
},
|
|
22
22
|
"homepage": "https://github.com/live-change/pattern-db",
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"tape": "^
|
|
24
|
+
"tape": "^5.7.4"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@live-change/pattern": "^0.
|
|
28
|
-
}
|
|
27
|
+
"@live-change/pattern": "^0.8.2"
|
|
28
|
+
},
|
|
29
|
+
"gitHead": "53b8efc8ec7f5c1c4af33077d8fb4a8a5580f1d9"
|
|
29
30
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const test = require('tape')
|
|
2
|
+
const testDb = require('./testDb.js')
|
|
3
|
+
const lcp = require('@live-change/pattern')
|
|
4
|
+
const { relationsStore } = require('../lib/relations-store.js')
|
|
5
|
+
|
|
6
|
+
let model
|
|
7
|
+
|
|
8
|
+
test("compile fail2ban chain with expire", (t) => {
|
|
9
|
+
|
|
10
|
+
t.plan(2)
|
|
11
|
+
|
|
12
|
+
let { model: compiled, last } = lcp.chain([
|
|
13
|
+
"failed-login",
|
|
14
|
+
{ eq: "ip", expire: "2m" },
|
|
15
|
+
"failed-login"
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
compiled.elements[last].actions = [ 'ban' ]
|
|
19
|
+
|
|
20
|
+
console.log(JSON.stringify(compiled, null, ' '))
|
|
21
|
+
|
|
22
|
+
model = compiled
|
|
23
|
+
|
|
24
|
+
t.deepEqual(model, {
|
|
25
|
+
"elements": {
|
|
26
|
+
"failed-login": {
|
|
27
|
+
"type": "failed-login",
|
|
28
|
+
"id": "failed-login",
|
|
29
|
+
"prev": [],
|
|
30
|
+
"next": [
|
|
31
|
+
"failed-login/ip@[ip|wait:2m]/failed-login",
|
|
32
|
+
"failed-login/wait:2m@[ip|wait:2m]"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"failed-login/ip|wait:2m/failed-login": {
|
|
36
|
+
"type": "failed-login",
|
|
37
|
+
"id": "failed-login/ip|wait:2m/failed-login",
|
|
38
|
+
"prev": [
|
|
39
|
+
"failed-login/ip@[ip|wait:2m]/failed-login",
|
|
40
|
+
"failed-login/wait:2m@[ip|wait:2m]"
|
|
41
|
+
],
|
|
42
|
+
"next": [],
|
|
43
|
+
"actions": ['ban']
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"relations": {
|
|
47
|
+
"failed-login/ip@[ip|wait:2m]/failed-login": {
|
|
48
|
+
"eq": [
|
|
49
|
+
{
|
|
50
|
+
"prev": "ip",
|
|
51
|
+
"next": "ip"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"id": "failed-login/ip@[ip|wait:2m]/failed-login",
|
|
55
|
+
"cancel": [
|
|
56
|
+
"failed-login/wait:2m@[ip|wait:2m]"
|
|
57
|
+
],
|
|
58
|
+
"prev": [
|
|
59
|
+
"failed-login"
|
|
60
|
+
],
|
|
61
|
+
"next": [
|
|
62
|
+
"failed-login/ip|wait:2m/failed-login"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"failed-login/wait:2m@[ip|wait:2m]": {
|
|
66
|
+
"id": "failed-login/wait:2m@[ip|wait:2m]",
|
|
67
|
+
"wait": "2m",
|
|
68
|
+
"cancel": [
|
|
69
|
+
"failed-login/ip@[ip|wait:2m]/failed-login",
|
|
70
|
+
"failed-login/wait:2m@[ip|wait:2m]"
|
|
71
|
+
],
|
|
72
|
+
"prev": [
|
|
73
|
+
"failed-login"
|
|
74
|
+
],
|
|
75
|
+
"next": []
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
t.test('live processor', async (t) => {
|
|
81
|
+
t.plan(4)
|
|
82
|
+
|
|
83
|
+
const db = await testDb()
|
|
84
|
+
const store = relationsStore(db, 'test', 'relations')
|
|
85
|
+
await store.createTable()
|
|
86
|
+
|
|
87
|
+
lcp.prepareModelForLive(model)
|
|
88
|
+
const processor = new lcp.LiveProcessor(model, store)
|
|
89
|
+
const ip = (Math.random()*1000).toFixed()
|
|
90
|
+
let time = 0
|
|
91
|
+
|
|
92
|
+
t.test('push first event', async (t) => {
|
|
93
|
+
t.plan(1)
|
|
94
|
+
const actions = await processor.processEvent({ id: 1, type: 'failed-login', keys: { ip }, time }, 0)
|
|
95
|
+
console.log("ACTIONS", actions)
|
|
96
|
+
const relations = await store.getRelations('failed-login', { ip })
|
|
97
|
+
//console.log("RELATIONS", JSON.stringify(relations, null, " "))
|
|
98
|
+
if(relations.length > 0) {
|
|
99
|
+
t.pass('processed')
|
|
100
|
+
} else {
|
|
101
|
+
t.fail('no reaction')
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
t.test('wait 2.5m for expire', async (t) => {
|
|
106
|
+
t.plan(1)
|
|
107
|
+
time += 2.5 * 60 * 1000
|
|
108
|
+
const actions = await processor.processTime(time)
|
|
109
|
+
console.log("ACTIONS", actions)
|
|
110
|
+
const relations = await store.getRelations('failed-login', { ip })
|
|
111
|
+
if(relations.length == 0) {
|
|
112
|
+
t.pass('expired')
|
|
113
|
+
} else {
|
|
114
|
+
t.fail('still exists')
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
t.test('push second event', async (t) => {
|
|
119
|
+
t.plan(1)
|
|
120
|
+
time += 1000
|
|
121
|
+
const actions = await processor.processEvent({ id: 2, type: 'failed-login', keys: { ip }, time }, 0)
|
|
122
|
+
console.log("ACTIONS", actions)
|
|
123
|
+
const relations = await store.getRelations('failed-login', { ip })
|
|
124
|
+
if(relations.length > 0) {
|
|
125
|
+
t.pass('processed')
|
|
126
|
+
} else {
|
|
127
|
+
t.fail('no reaction')
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
t.test('push third event', async (t) => {
|
|
132
|
+
t.plan(1)
|
|
133
|
+
time += 1000
|
|
134
|
+
const actions = await processor.processEvent({ id:3, type: 'failed-login', keys: { ip }, time }, 0)
|
|
135
|
+
console.log("ACTIONS", actions)
|
|
136
|
+
t.deepEqual(actions, ['ban'], 'actions match')
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
const test = require('tape')
|
|
2
|
+
const testDb = require('./testDb.js')
|
|
3
|
+
const lcp = require('@live-change/pattern')
|
|
4
|
+
const { relationsStore } = require('../lib/relations-store.js')
|
|
5
|
+
let model
|
|
6
|
+
|
|
7
|
+
test("compile fail2ban chain with expire", (t) => {
|
|
8
|
+
|
|
9
|
+
t.plan(2)
|
|
10
|
+
|
|
11
|
+
let { model: compiled } =
|
|
12
|
+
lcp.first({ id: "first-failed-attempt", type: "failed-login" })
|
|
13
|
+
.link({ eq: "ip", expire: "2m" },
|
|
14
|
+
lcp.first({ id: "second-failed-attempt", type: "failed-login", actions: ['ban'] })
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
console.log(JSON.stringify(compiled, null, ' '))
|
|
18
|
+
|
|
19
|
+
model = compiled
|
|
20
|
+
|
|
21
|
+
t.deepEqual(model, {
|
|
22
|
+
"elements": {
|
|
23
|
+
"first-failed-attempt": {
|
|
24
|
+
"id": "first-failed-attempt",
|
|
25
|
+
"type": "failed-login",
|
|
26
|
+
"prev": [],
|
|
27
|
+
"next": [
|
|
28
|
+
"first-failed-attempt/ip@[ip|wait:2m]/second-failed-attempt",
|
|
29
|
+
"first-failed-attempt/wait:2m@[ip|wait:2m]"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"second-failed-attempt": {
|
|
33
|
+
"id": "second-failed-attempt",
|
|
34
|
+
"type": "failed-login",
|
|
35
|
+
"actions": [
|
|
36
|
+
"ban"
|
|
37
|
+
],
|
|
38
|
+
"prev": [
|
|
39
|
+
"first-failed-attempt/ip@[ip|wait:2m]/second-failed-attempt"
|
|
40
|
+
],
|
|
41
|
+
"next": []
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"relations": {
|
|
45
|
+
"first-failed-attempt/ip@[ip|wait:2m]/second-failed-attempt": {
|
|
46
|
+
"eq": [
|
|
47
|
+
{
|
|
48
|
+
"prev": "ip",
|
|
49
|
+
"next": "ip"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"id": "first-failed-attempt/ip@[ip|wait:2m]/second-failed-attempt",
|
|
53
|
+
"cancel": [
|
|
54
|
+
"first-failed-attempt/wait:2m@[ip|wait:2m]"
|
|
55
|
+
],
|
|
56
|
+
"prev": [
|
|
57
|
+
"first-failed-attempt"
|
|
58
|
+
],
|
|
59
|
+
"next": [
|
|
60
|
+
"second-failed-attempt"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
"first-failed-attempt/wait:2m@[ip|wait:2m]": {
|
|
64
|
+
"id": "first-failed-attempt/wait:2m@[ip|wait:2m]",
|
|
65
|
+
"wait": "2m",
|
|
66
|
+
"cancel": [
|
|
67
|
+
"first-failed-attempt/ip@[ip|wait:2m]/second-failed-attempt",
|
|
68
|
+
"first-failed-attempt/wait:2m@[ip|wait:2m]"
|
|
69
|
+
],
|
|
70
|
+
"prev": [
|
|
71
|
+
"first-failed-attempt"
|
|
72
|
+
],
|
|
73
|
+
"next": []
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}, "compiled ok")
|
|
77
|
+
|
|
78
|
+
t.test('live processor', async (t) => {
|
|
79
|
+
t.plan(4)
|
|
80
|
+
|
|
81
|
+
const db = await testDb()
|
|
82
|
+
const store = relationsStore(db, 'test', 'relations')
|
|
83
|
+
await store.createTable()
|
|
84
|
+
|
|
85
|
+
lcp.prepareModelForLive(model)
|
|
86
|
+
const processor = new lcp.LiveProcessor(model, store)
|
|
87
|
+
const ip = (Math.random()*1000).toFixed()
|
|
88
|
+
|
|
89
|
+
let time = 0
|
|
90
|
+
|
|
91
|
+
t.test('push first event', async (t) => {
|
|
92
|
+
t.plan(1)
|
|
93
|
+
const actions = await processor.processEvent({ type: 'failed-login', keys: { ip }, time }, 0)
|
|
94
|
+
console.log("ACTIONS", actions)
|
|
95
|
+
const relations = await store.getRelations('failed-login', { ip })
|
|
96
|
+
//console.log("RELATIONS", JSON.stringify(relations, null, " "))
|
|
97
|
+
if(relations.length > 0) {
|
|
98
|
+
t.pass('processed')
|
|
99
|
+
} else {
|
|
100
|
+
t.fail('no reaction')
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
t.test('wait 2.5m for expire', async (t) => {
|
|
105
|
+
t.plan(1)
|
|
106
|
+
time += 2.5 * 60 * 1000
|
|
107
|
+
const actions = await processor.processTime(time)
|
|
108
|
+
console.log("ACTIONS", actions)
|
|
109
|
+
const relations = await store.getRelations('failed-login', { ip })
|
|
110
|
+
//console.log("RELATIONS", JSON.stringify(relations, null, " "))
|
|
111
|
+
if(relations.length == 0) {
|
|
112
|
+
t.pass('expired')
|
|
113
|
+
} else {
|
|
114
|
+
t.fail('still exists')
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
t.test('push second event', async (t) => {
|
|
119
|
+
t.plan(1)
|
|
120
|
+
time += 1000
|
|
121
|
+
const actions = await processor.processEvent({ type: 'failed-login', keys: { ip }, time }, 0)
|
|
122
|
+
console.log("ACTIONS", actions)
|
|
123
|
+
const relations = await store.getRelations('failed-login', { ip })
|
|
124
|
+
//console.log("RELATIONS", JSON.stringify(relations, null, " "))
|
|
125
|
+
if(relations.length > 0) {
|
|
126
|
+
t.pass('processed')
|
|
127
|
+
} else {
|
|
128
|
+
t.fail('no reaction')
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
t.test('push third event', async (t) => {
|
|
133
|
+
t.plan(1)
|
|
134
|
+
time += 1000
|
|
135
|
+
const actions = await processor.processEvent({ type: 'failed-login', keys: { ip }, time }, 0)
|
|
136
|
+
console.log("ACTIONS", actions)
|
|
137
|
+
t.deepEqual(actions, ['ban'],'actions match')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const test = require('tape')
|
|
2
|
+
const testDb = require('./testDb.js')
|
|
3
|
+
const lcp = require('@live-change/pattern')
|
|
4
|
+
const { relationsStore } = require('../lib/relations-store.js')
|
|
5
|
+
|
|
6
|
+
let model
|
|
7
|
+
|
|
8
|
+
test("compile simple chain", (t) => {
|
|
9
|
+
|
|
10
|
+
t.plan(2)
|
|
11
|
+
|
|
12
|
+
let { model: compiled } =
|
|
13
|
+
lcp.first({ id: 'visitor', type: 'enter-website' })
|
|
14
|
+
.link("sessionId", lcp.first({ id: 'started-registration', type: 'start-register' }))
|
|
15
|
+
.link("userId", lcp.first({ id: 'registered', type: 'finish-register' }))
|
|
16
|
+
|
|
17
|
+
console.log(JSON.stringify(compiled, null, ' '))
|
|
18
|
+
|
|
19
|
+
model = compiled
|
|
20
|
+
|
|
21
|
+
t.deepEqual(model, {
|
|
22
|
+
"elements": {
|
|
23
|
+
"visitor": {
|
|
24
|
+
"id": "visitor",
|
|
25
|
+
"type": "enter-website",
|
|
26
|
+
"prev": [],
|
|
27
|
+
"next": [
|
|
28
|
+
"visitor/sessionId/started-registration"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"started-registration": {
|
|
32
|
+
"id": "started-registration",
|
|
33
|
+
"type": "start-register",
|
|
34
|
+
"prev": [
|
|
35
|
+
"visitor/sessionId/started-registration"
|
|
36
|
+
],
|
|
37
|
+
"next": [
|
|
38
|
+
"started-registration/userId/registered"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"registered": {
|
|
42
|
+
"id": "registered",
|
|
43
|
+
"type": "finish-register",
|
|
44
|
+
"prev": [
|
|
45
|
+
"started-registration/userId/registered"
|
|
46
|
+
],
|
|
47
|
+
"next": []
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"relations": {
|
|
51
|
+
"visitor/sessionId/started-registration": {
|
|
52
|
+
"eq": [
|
|
53
|
+
{
|
|
54
|
+
"prev": "sessionId",
|
|
55
|
+
"next": "sessionId"
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"id": "visitor/sessionId/started-registration",
|
|
59
|
+
"prev": [
|
|
60
|
+
"visitor"
|
|
61
|
+
],
|
|
62
|
+
"next": [
|
|
63
|
+
"started-registration"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
"started-registration/userId/registered": {
|
|
67
|
+
"eq": [
|
|
68
|
+
{
|
|
69
|
+
"prev": "userId",
|
|
70
|
+
"next": "userId"
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"id": "started-registration/userId/registered",
|
|
74
|
+
"prev": [
|
|
75
|
+
"started-registration"
|
|
76
|
+
],
|
|
77
|
+
"next": [
|
|
78
|
+
"registered"
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}, 'model compiled')
|
|
83
|
+
|
|
84
|
+
t.test('live processor', async (t) => {
|
|
85
|
+
t.plan(2)
|
|
86
|
+
|
|
87
|
+
const db = await testDb()
|
|
88
|
+
const store = relationsStore(db, 'test', 'relations')
|
|
89
|
+
await store.createTable()
|
|
90
|
+
|
|
91
|
+
lcp.prepareModelForLive(model)
|
|
92
|
+
const processor = new lcp.LiveProcessor(model, store)
|
|
93
|
+
const sessionId = (Math.random()*1000).toFixed()
|
|
94
|
+
const userId = (Math.random()*1000).toFixed()
|
|
95
|
+
|
|
96
|
+
t.test('push first event', async (t) => {
|
|
97
|
+
t.plan(1)
|
|
98
|
+
await processor.processEvent({ type: 'enter-website', keys: { sessionId }, time: 0 })
|
|
99
|
+
const relations = await store.getRelations('start-register', { sessionId })
|
|
100
|
+
//console.log("RELATIONS", JSON.stringify(relations, null, " "))
|
|
101
|
+
if(relations.length > 0) {
|
|
102
|
+
t.pass('processed')
|
|
103
|
+
} else {
|
|
104
|
+
t.fail('no reaction')
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
t.test('push second event', async (t) => {
|
|
109
|
+
t.plan(1)
|
|
110
|
+
await processor.processEvent({ type: 'start-register', keys: { sessionId, userId }, time: 100 })
|
|
111
|
+
const relations = await store.getRelations('finish-register', { userId })
|
|
112
|
+
//console.log("RELATIONS", JSON.stringify(relations, null, " "))
|
|
113
|
+
if(relations.length > 0) {
|
|
114
|
+
t.pass('processed')
|
|
115
|
+
} else {
|
|
116
|
+
t.fail('no reaction')
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
|