hive-stream 2.0.5 → 3.0.0
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/.claude/settings.local.json +12 -0
- package/.env.example +2 -2
- package/.travis.yml +11 -11
- package/CHANGELOG.md +166 -0
- package/CLAUDE.md +75 -0
- package/LICENSE +21 -21
- package/README.md +338 -238
- package/dist/actions.d.ts +41 -10
- package/dist/actions.js +126 -23
- package/dist/actions.js.map +1 -1
- package/dist/adapters/base.adapter.d.ts +25 -25
- package/dist/adapters/base.adapter.js +63 -49
- package/dist/adapters/base.adapter.js.map +1 -1
- package/dist/adapters/mongodb.adapter.d.ts +50 -37
- package/dist/adapters/mongodb.adapter.js +363 -158
- package/dist/adapters/mongodb.adapter.js.map +1 -1
- package/dist/adapters/postgresql.adapter.d.ts +49 -0
- package/dist/adapters/postgresql.adapter.js +507 -0
- package/dist/adapters/postgresql.adapter.js.map +1 -0
- package/dist/adapters/sqlite.adapter.d.ts +40 -41
- package/dist/adapters/sqlite.adapter.js +470 -397
- package/dist/adapters/sqlite.adapter.js.map +1 -1
- package/dist/api.d.ts +6 -6
- package/dist/api.js +95 -55
- package/dist/api.js.map +1 -1
- package/dist/config.d.ts +16 -16
- package/dist/config.js +18 -18
- package/dist/config.js.map +1 -1
- package/dist/contracts/coinflip.contract.d.ts +27 -14
- package/dist/contracts/coinflip.contract.js +253 -94
- package/dist/contracts/coinflip.contract.js.map +1 -1
- package/dist/contracts/dice.contract.d.ts +37 -29
- package/dist/contracts/dice.contract.js +282 -155
- package/dist/contracts/dice.contract.js.map +1 -1
- package/dist/contracts/lotto.contract.d.ts +20 -20
- package/dist/contracts/lotto.contract.js +246 -246
- package/dist/contracts/nft.contract.d.ts +24 -0
- package/dist/contracts/nft.contract.js +533 -0
- package/dist/contracts/nft.contract.js.map +1 -0
- package/dist/contracts/token.contract.d.ts +18 -0
- package/dist/contracts/token.contract.js +263 -0
- package/dist/contracts/token.contract.js.map +1 -0
- package/dist/exchanges/bittrex.d.ts +6 -6
- package/dist/exchanges/bittrex.js +34 -34
- package/dist/exchanges/coingecko.d.ts +5 -0
- package/dist/exchanges/coingecko.js +40 -0
- package/dist/exchanges/coingecko.js.map +1 -0
- package/dist/exchanges/exchange.d.ts +9 -9
- package/dist/exchanges/exchange.js +26 -26
- package/dist/hive-rates.d.ts +9 -9
- package/dist/hive-rates.js +121 -75
- package/dist/hive-rates.js.map +1 -1
- package/dist/index.d.ts +12 -11
- package/dist/index.js +33 -32
- package/dist/index.js.map +1 -1
- package/dist/streamer.d.ts +140 -93
- package/dist/streamer.js +793 -545
- package/dist/streamer.js.map +1 -1
- package/dist/test.d.ts +1 -1
- package/dist/test.js +25 -25
- package/dist/test.js.map +1 -1
- package/dist/types/hive-stream.d.ts +35 -6
- package/dist/types/hive-stream.js +2 -2
- package/dist/utils.d.ts +27 -27
- package/dist/utils.js +271 -261
- package/dist/utils.js.map +1 -1
- package/ecosystem.config.js +17 -17
- package/jest.config.js +8 -8
- package/package.json +53 -48
- package/test-contract-block.md +18 -18
- package/tests/actions.spec.ts +252 -0
- package/tests/adapters/actions-persistence.spec.ts +144 -0
- package/tests/adapters/postgresql.adapter.spec.ts +127 -0
- package/tests/adapters/sqlite.adapter.spec.ts +180 -42
- package/tests/contracts/coinflip.contract.spec.ts +221 -131
- package/tests/contracts/dice.contract.spec.ts +202 -159
- package/tests/contracts/entrants.json +728 -728
- package/tests/contracts/lotto.contract.spec.ts +323 -323
- package/tests/contracts/nft.contract.spec.ts +948 -0
- package/tests/contracts/token.contract.spec.ts +334 -0
- package/tests/helpers/mock-adapter.ts +214 -0
- package/tests/setup.ts +29 -18
- package/tests/streamer-actions.spec.ts +263 -0
- package/tests/streamer.spec.ts +248 -151
- package/tests/utils.spec.ts +91 -94
- package/tsconfig.build.json +3 -22
- package/tslint.json +20 -20
- package/wallaby.js +26 -26
- package/.env +0 -3
package/ecosystem.config.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
// PM2 configuration
|
|
2
|
-
|
|
3
|
-
module.exports = {
|
|
4
|
-
apps: [
|
|
5
|
-
{
|
|
6
|
-
name: 'steem-stream',
|
|
7
|
-
script: 'index.js',
|
|
8
|
-
ignore_watch: ['node_modules'],
|
|
9
|
-
env: {
|
|
10
|
-
NODE_ENV: 'development'
|
|
11
|
-
},
|
|
12
|
-
env_production: {
|
|
13
|
-
NODE_ENV: 'production'
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
]
|
|
17
|
-
};
|
|
1
|
+
// PM2 configuration
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
apps: [
|
|
5
|
+
{
|
|
6
|
+
name: 'steem-stream',
|
|
7
|
+
script: 'index.js',
|
|
8
|
+
ignore_watch: ['node_modules'],
|
|
9
|
+
env: {
|
|
10
|
+
NODE_ENV: 'development'
|
|
11
|
+
},
|
|
12
|
+
env_production: {
|
|
13
|
+
NODE_ENV: 'production'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
};
|
package/jest.config.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
setupFilesAfterEnv: ["<rootDir>/tests/setup.ts"],
|
|
3
|
-
moduleFileExtensions: ['ts', 'js'],
|
|
4
|
-
transform: {
|
|
5
|
-
'^.+\\.(ts|tsx)$': 'ts-jest'
|
|
6
|
-
},
|
|
7
|
-
testMatch: ['**/tests/**/*.spec.(ts)'],
|
|
8
|
-
testEnvironment: 'node'
|
|
1
|
+
module.exports = {
|
|
2
|
+
setupFilesAfterEnv: ["<rootDir>/tests/setup.ts"],
|
|
3
|
+
moduleFileExtensions: ['ts', 'js'],
|
|
4
|
+
transform: {
|
|
5
|
+
'^.+\\.(ts|tsx)$': 'ts-jest'
|
|
6
|
+
},
|
|
7
|
+
testMatch: ['**/tests/**/*.spec.(ts)'],
|
|
8
|
+
testEnvironment: 'node'
|
|
9
9
|
};
|
package/package.json
CHANGED
|
@@ -1,48 +1,53 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "hive-stream",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A layer for streaming actions on the Hive blockchain and reacting to them.",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"repository": "git@github.com:Vheissu/hive-stream.git",
|
|
8
|
-
"author": "Dwayne Charrington <dwaynecharrington@gmail.com>",
|
|
9
|
-
"license": "MIT",
|
|
10
|
-
"private": false,
|
|
11
|
-
"scripts": {
|
|
12
|
-
"build": "tsc --project tsconfig.build.json",
|
|
13
|
-
"start": "npx ts-node src/test.ts",
|
|
14
|
-
"watch": "tsc -w",
|
|
15
|
-
"test": "jest --verbose",
|
|
16
|
-
"clean-tests": "jest --clearCache"
|
|
17
|
-
},
|
|
18
|
-
"dependencies": {
|
|
19
|
-
"@hiveio/dhive": "^1.2.4",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"@types/
|
|
37
|
-
"@types/
|
|
38
|
-
"@types/
|
|
39
|
-
"@types/
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "hive-stream",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "A layer for streaming actions on the Hive blockchain and reacting to them.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"repository": "git@github.com:Vheissu/hive-stream.git",
|
|
8
|
+
"author": "Dwayne Charrington <dwaynecharrington@gmail.com>",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"private": false,
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc --project tsconfig.build.json",
|
|
13
|
+
"start": "npx ts-node src/test.ts",
|
|
14
|
+
"watch": "tsc -w",
|
|
15
|
+
"test": "jest --verbose",
|
|
16
|
+
"clean-tests": "jest --clearCache"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@hiveio/dhive": "^1.2.4",
|
|
20
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
21
|
+
"better-sqlite3": "^12.2.0",
|
|
22
|
+
"bignumber.js": "^9.1.0",
|
|
23
|
+
"cors": "^2.8.5",
|
|
24
|
+
"dotenv": "^16.0.3",
|
|
25
|
+
"express": "^4.18.2",
|
|
26
|
+
"knex": "^3.1.0",
|
|
27
|
+
"moment": "^2.29.4",
|
|
28
|
+
"mongodb": "^4.11.0",
|
|
29
|
+
"node-fetch": "^3.2.10",
|
|
30
|
+
"pg": "^8.16.3",
|
|
31
|
+
"seedrandom": "^3.0.5",
|
|
32
|
+
"sscjs": "^0.0.9",
|
|
33
|
+
"uuid": "^9.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/bytebuffer": "^5.0.44",
|
|
37
|
+
"@types/dotenv": "^8.2.0",
|
|
38
|
+
"@types/jest": "^29.2.3",
|
|
39
|
+
"@types/mongodb": "^4.0.7",
|
|
40
|
+
"@types/node": "^18.19.121",
|
|
41
|
+
"@types/node-cron": "^3.0.5",
|
|
42
|
+
"@types/pg": "^8.15.5",
|
|
43
|
+
"@types/seedrandom": "^3.0.8",
|
|
44
|
+
"@types/uuid": "^10.0.0",
|
|
45
|
+
"jest": "^29.3.1",
|
|
46
|
+
"jest-cli": "^29.3.1",
|
|
47
|
+
"jest-fetch-mock": "^3.0.3",
|
|
48
|
+
"mongodb-memory-server": "^8.9.5",
|
|
49
|
+
"ts-jest": "^29.0.3",
|
|
50
|
+
"ts-node": "^10.9.1",
|
|
51
|
+
"typescript": "^4.9.3"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/test-contract-block.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
42189228
|
|
2
|
-
|
|
3
|
-
hive_keychain.requestCustomJson('beggars', 'hivedice', 'active', JSON.stringify({ hiveContract: { name: 'hivedice', action: 'roll', payload: { roll: 22, direction: 'lesserThan'} } }), 'Test', function(response) {
|
|
4
|
-
console.log(response);
|
|
5
|
-
});
|
|
6
|
-
|
|
7
|
-
42203941
|
|
8
|
-
Transfer memo payload
|
|
9
|
-
|
|
10
|
-
{"hiveContract":{"id":"testdice", "name":"hivedice","action":"roll","payload":{"roll":95,"direction":"greaterThan"}}}
|
|
11
|
-
|
|
12
|
-
42208330
|
|
13
|
-
Transfer memo, amount higher than max
|
|
14
|
-
|
|
15
|
-
42209768
|
|
16
|
-
Transfer memo, valid bet
|
|
17
|
-
|
|
18
|
-
42210252
|
|
1
|
+
42189228
|
|
2
|
+
|
|
3
|
+
hive_keychain.requestCustomJson('beggars', 'hivedice', 'active', JSON.stringify({ hiveContract: { name: 'hivedice', action: 'roll', payload: { roll: 22, direction: 'lesserThan'} } }), 'Test', function(response) {
|
|
4
|
+
console.log(response);
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
42203941
|
|
8
|
+
Transfer memo payload
|
|
9
|
+
|
|
10
|
+
{"hiveContract":{"id":"testdice", "name":"hivedice","action":"roll","payload":{"roll":95,"direction":"greaterThan"}}}
|
|
11
|
+
|
|
12
|
+
42208330
|
|
13
|
+
Transfer memo, amount higher than max
|
|
14
|
+
|
|
15
|
+
42209768
|
|
16
|
+
Transfer memo, valid bet
|
|
17
|
+
|
|
18
|
+
42210252
|
|
19
19
|
Transfer memo, valid bet and greater than
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { TimeAction } from '../src/actions';
|
|
2
|
+
|
|
3
|
+
describe('TimeAction', () => {
|
|
4
|
+
describe('Constructor and Validation', () => {
|
|
5
|
+
test('Should create a valid TimeAction with required parameters', () => {
|
|
6
|
+
const action = new TimeAction('1m', 'test-action', 'testcontract', 'testmethod');
|
|
7
|
+
|
|
8
|
+
expect(action.timeValue).toBe('1m');
|
|
9
|
+
expect(action.id).toBe('test-action');
|
|
10
|
+
expect(action.contractName).toBe('testcontract');
|
|
11
|
+
expect(action.contractMethod).toBe('testmethod');
|
|
12
|
+
expect(action.payload).toEqual({});
|
|
13
|
+
expect(action.enabled).toBe(true);
|
|
14
|
+
expect(action.executionCount).toBe(0);
|
|
15
|
+
expect(action.date).toBeInstanceOf(Date);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('Should create TimeAction with all optional parameters', () => {
|
|
19
|
+
const testDate = new Date('2023-01-01T00:00:00Z');
|
|
20
|
+
const payload = { test: 'data' };
|
|
21
|
+
|
|
22
|
+
const action = new TimeAction(
|
|
23
|
+
'1h',
|
|
24
|
+
'test-action',
|
|
25
|
+
'testcontract',
|
|
26
|
+
'testmethod',
|
|
27
|
+
payload,
|
|
28
|
+
testDate,
|
|
29
|
+
false,
|
|
30
|
+
5,
|
|
31
|
+
10,
|
|
32
|
+
'America/New_York'
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
expect(action.timeValue).toBe('1h');
|
|
36
|
+
expect(action.payload).toEqual(payload);
|
|
37
|
+
expect(action.date).toEqual(testDate);
|
|
38
|
+
expect(action.enabled).toBe(false);
|
|
39
|
+
expect(action.executionCount).toBe(5);
|
|
40
|
+
expect(action.maxExecutions).toBe(10);
|
|
41
|
+
expect(action.timezone).toBe('America/New_York');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('Should throw error for invalid timeValue', () => {
|
|
45
|
+
expect(() => {
|
|
46
|
+
new TimeAction('invalid', 'test-action', 'testcontract', 'testmethod');
|
|
47
|
+
}).toThrow('Invalid timeValue \'invalid\'');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('Should throw error for empty timeValue', () => {
|
|
51
|
+
expect(() => {
|
|
52
|
+
new TimeAction('', 'test-action', 'testcontract', 'testmethod');
|
|
53
|
+
}).toThrow('timeValue must be a non-empty string');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('Should throw error for invalid id format', () => {
|
|
57
|
+
expect(() => {
|
|
58
|
+
new TimeAction('1m', 'test action!', 'testcontract', 'testmethod');
|
|
59
|
+
}).toThrow('id can only contain alphanumeric characters, underscores, and hyphens');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('Should throw error for too long id', () => {
|
|
63
|
+
const longId = 'a'.repeat(256);
|
|
64
|
+
expect(() => {
|
|
65
|
+
new TimeAction('1m', longId, 'testcontract', 'testmethod');
|
|
66
|
+
}).toThrow('id must not exceed 255 characters');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('Should throw error for empty contractName', () => {
|
|
70
|
+
expect(() => {
|
|
71
|
+
new TimeAction('1m', 'test-action', '', 'testmethod');
|
|
72
|
+
}).toThrow('contractName must be a non-empty string');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('Should throw error for empty contractMethod', () => {
|
|
76
|
+
expect(() => {
|
|
77
|
+
new TimeAction('1m', 'test-action', 'testcontract', '');
|
|
78
|
+
}).toThrow('contractMethod must be a non-empty string');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('Should throw error for invalid date string', () => {
|
|
82
|
+
expect(() => {
|
|
83
|
+
new TimeAction('1m', 'test-action', 'testcontract', 'testmethod', {}, 'invalid-date');
|
|
84
|
+
}).toThrow('Invalid date string \'invalid-date\'');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('Methods', () => {
|
|
89
|
+
let action: TimeAction;
|
|
90
|
+
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
action = new TimeAction('1m', 'test-action', 'testcontract', 'testmethod');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('Should reset date and lastExecution', async () => {
|
|
96
|
+
const originalDate = action.date;
|
|
97
|
+
action.lastExecution = new Date();
|
|
98
|
+
|
|
99
|
+
// Wait a bit to ensure time difference
|
|
100
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
101
|
+
|
|
102
|
+
action.reset();
|
|
103
|
+
|
|
104
|
+
expect(action.date.getTime()).toBeGreaterThan(originalDate.getTime());
|
|
105
|
+
expect(action.lastExecution).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('Should disable and enable action', () => {
|
|
109
|
+
expect(action.enabled).toBe(true);
|
|
110
|
+
|
|
111
|
+
action.disable();
|
|
112
|
+
expect(action.enabled).toBe(false);
|
|
113
|
+
|
|
114
|
+
action.enable();
|
|
115
|
+
expect(action.enabled).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('Should track execution count and max executions', () => {
|
|
119
|
+
action.maxExecutions = 3;
|
|
120
|
+
|
|
121
|
+
expect(action.hasReachedMaxExecutions()).toBe(false);
|
|
122
|
+
|
|
123
|
+
action.incrementExecutionCount();
|
|
124
|
+
expect(action.executionCount).toBe(1);
|
|
125
|
+
expect(action.lastExecution).toBeInstanceOf(Date);
|
|
126
|
+
expect(action.hasReachedMaxExecutions()).toBe(false);
|
|
127
|
+
|
|
128
|
+
action.incrementExecutionCount();
|
|
129
|
+
action.incrementExecutionCount();
|
|
130
|
+
expect(action.executionCount).toBe(3);
|
|
131
|
+
expect(action.hasReachedMaxExecutions()).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('Should not reach max executions when maxExecutions is undefined', () => {
|
|
135
|
+
action.executionCount = 1000;
|
|
136
|
+
expect(action.hasReachedMaxExecutions()).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('Serialization', () => {
|
|
141
|
+
test('Should serialize to JSON correctly', () => {
|
|
142
|
+
const testDate = new Date('2023-01-01T00:00:00Z');
|
|
143
|
+
const lastExecution = new Date('2023-01-01T01:00:00Z');
|
|
144
|
+
const payload = { test: 'data' };
|
|
145
|
+
|
|
146
|
+
const action = new TimeAction(
|
|
147
|
+
'1h',
|
|
148
|
+
'test-action',
|
|
149
|
+
'testcontract',
|
|
150
|
+
'testmethod',
|
|
151
|
+
payload,
|
|
152
|
+
testDate,
|
|
153
|
+
false,
|
|
154
|
+
5,
|
|
155
|
+
10,
|
|
156
|
+
'UTC'
|
|
157
|
+
);
|
|
158
|
+
action.lastExecution = lastExecution;
|
|
159
|
+
|
|
160
|
+
const json = action.toJSON();
|
|
161
|
+
|
|
162
|
+
expect(json).toEqual({
|
|
163
|
+
timeValue: '1h',
|
|
164
|
+
id: 'test-action',
|
|
165
|
+
contractName: 'testcontract',
|
|
166
|
+
contractMethod: 'testmethod',
|
|
167
|
+
payload: payload,
|
|
168
|
+
date: testDate.toISOString(),
|
|
169
|
+
enabled: false,
|
|
170
|
+
lastExecution: lastExecution.toISOString(),
|
|
171
|
+
executionCount: 5,
|
|
172
|
+
maxExecutions: 10,
|
|
173
|
+
timezone: 'UTC'
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('Should deserialize from JSON correctly', () => {
|
|
178
|
+
const testDate = new Date('2023-01-01T00:00:00Z');
|
|
179
|
+
const lastExecution = new Date('2023-01-01T01:00:00Z');
|
|
180
|
+
const payload = { test: 'data' };
|
|
181
|
+
|
|
182
|
+
const jsonData = {
|
|
183
|
+
timeValue: '1h',
|
|
184
|
+
id: 'test-action',
|
|
185
|
+
contractName: 'testcontract',
|
|
186
|
+
contractMethod: 'testmethod',
|
|
187
|
+
payload: payload,
|
|
188
|
+
date: testDate.toISOString(),
|
|
189
|
+
enabled: false,
|
|
190
|
+
lastExecution: lastExecution.toISOString(),
|
|
191
|
+
executionCount: 5,
|
|
192
|
+
maxExecutions: 10,
|
|
193
|
+
timezone: 'UTC'
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const action = TimeAction.fromJSON(jsonData);
|
|
197
|
+
|
|
198
|
+
expect(action.timeValue).toBe('1h');
|
|
199
|
+
expect(action.id).toBe('test-action');
|
|
200
|
+
expect(action.contractName).toBe('testcontract');
|
|
201
|
+
expect(action.contractMethod).toBe('testmethod');
|
|
202
|
+
expect(action.payload).toEqual(payload);
|
|
203
|
+
expect(action.date).toEqual(testDate);
|
|
204
|
+
expect(action.enabled).toBe(false);
|
|
205
|
+
expect(action.lastExecution).toEqual(lastExecution);
|
|
206
|
+
expect(action.executionCount).toBe(5);
|
|
207
|
+
expect(action.maxExecutions).toBe(10);
|
|
208
|
+
expect(action.timezone).toBe('UTC');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('Should handle serialization without optional fields', () => {
|
|
212
|
+
const action = new TimeAction('1m', 'test-action', 'testcontract', 'testmethod');
|
|
213
|
+
|
|
214
|
+
const json = action.toJSON();
|
|
215
|
+
const restored = TimeAction.fromJSON(json);
|
|
216
|
+
|
|
217
|
+
expect(restored.timeValue).toBe(action.timeValue);
|
|
218
|
+
expect(restored.id).toBe(action.id);
|
|
219
|
+
expect(restored.contractName).toBe(action.contractName);
|
|
220
|
+
expect(restored.contractMethod).toBe(action.contractMethod);
|
|
221
|
+
expect(restored.payload).toEqual({});
|
|
222
|
+
expect(restored.enabled).toBe(true);
|
|
223
|
+
expect(restored.executionCount).toBe(0);
|
|
224
|
+
expect(restored.lastExecution).toBeUndefined();
|
|
225
|
+
expect(restored.maxExecutions).toBeUndefined();
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('Static Methods', () => {
|
|
230
|
+
test('Should return valid time values', () => {
|
|
231
|
+
const validValues = TimeAction.getValidTimeValues();
|
|
232
|
+
|
|
233
|
+
expect(validValues).toContain('3s');
|
|
234
|
+
expect(validValues).toContain('1m');
|
|
235
|
+
expect(validValues).toContain('1h');
|
|
236
|
+
expect(validValues).toContain('24h');
|
|
237
|
+
expect(validValues).toContain('week');
|
|
238
|
+
expect(Array.isArray(validValues)).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('Should return a copy of valid time values', () => {
|
|
242
|
+
const validValues1 = TimeAction.getValidTimeValues();
|
|
243
|
+
const validValues2 = TimeAction.getValidTimeValues();
|
|
244
|
+
|
|
245
|
+
expect(validValues1).toEqual(validValues2);
|
|
246
|
+
expect(validValues1).not.toBe(validValues2); // Different array instances
|
|
247
|
+
|
|
248
|
+
validValues1.push('invalid');
|
|
249
|
+
expect(validValues2).not.toContain('invalid');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { TimeAction } from '../../src/actions';
|
|
2
|
+
import { SqliteAdapter } from '../../src/adapters/sqlite.adapter';
|
|
3
|
+
import { MongodbAdapter } from '../../src/adapters/mongodb.adapter';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
describe('Actions Persistence', () => {
|
|
8
|
+
describe('SqliteAdapter', () => {
|
|
9
|
+
let adapter: SqliteAdapter;
|
|
10
|
+
let testDbPath: string;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
// Create unique database path for each test
|
|
14
|
+
testDbPath = path.resolve(__dirname, `../../src/adapters/hive-stream-test-${Date.now()}-${Math.random()}.db`);
|
|
15
|
+
|
|
16
|
+
// Clean up any existing test database
|
|
17
|
+
if (fs.existsSync(testDbPath)) {
|
|
18
|
+
fs.unlinkSync(testDbPath);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
adapter = new SqliteAdapter(testDbPath);
|
|
22
|
+
await adapter.create();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
if (adapter && adapter.getDb()) {
|
|
27
|
+
await adapter.destroy();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Clean up test database
|
|
31
|
+
if (fs.existsSync(testDbPath)) {
|
|
32
|
+
fs.unlinkSync(testDbPath);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('Should save and load actions correctly', async () => {
|
|
37
|
+
const testActions = [
|
|
38
|
+
new TimeAction('1m', 'action1', 'contract1', 'method1', { data: 'test1' }),
|
|
39
|
+
new TimeAction('5m', 'action2', 'contract2', 'method2', { data: 'test2' }, new Date('2023-01-01'), false, 5, 10)
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// Serialize actions as they would be in the streamer
|
|
43
|
+
const serializedActions = testActions.map(a => a.toJSON());
|
|
44
|
+
|
|
45
|
+
const stateData = {
|
|
46
|
+
lastBlockNumber: 12345,
|
|
47
|
+
actions: serializedActions
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
await adapter.saveState(stateData);
|
|
51
|
+
|
|
52
|
+
const loadedActions = await adapter.loadActions();
|
|
53
|
+
|
|
54
|
+
expect(loadedActions).toHaveLength(2);
|
|
55
|
+
|
|
56
|
+
const action1 = loadedActions.find(a => a.id === 'action1');
|
|
57
|
+
const action2 = loadedActions.find(a => a.id === 'action2');
|
|
58
|
+
|
|
59
|
+
expect(action1).toBeDefined();
|
|
60
|
+
expect(action1?.timeValue).toBe('1m');
|
|
61
|
+
expect(action1?.contractName).toBe('contract1');
|
|
62
|
+
expect(action1?.contractMethod).toBe('method1');
|
|
63
|
+
expect(action1?.payload).toEqual({ data: 'test1' });
|
|
64
|
+
expect(action1?.enabled).toBe(true);
|
|
65
|
+
expect(action1?.executionCount).toBe(0);
|
|
66
|
+
|
|
67
|
+
expect(action2).toBeDefined();
|
|
68
|
+
expect(action2?.timeValue).toBe('5m');
|
|
69
|
+
expect(action2?.contractName).toBe('contract2');
|
|
70
|
+
expect(action2?.contractMethod).toBe('method2');
|
|
71
|
+
expect(action2?.payload).toEqual({ data: 'test2' });
|
|
72
|
+
expect(action2?.enabled).toBe(false);
|
|
73
|
+
expect(action2?.executionCount).toBe(5);
|
|
74
|
+
expect(action2?.maxExecutions).toBe(10);
|
|
75
|
+
expect(action2?.date).toEqual(new Date('2023-01-01'));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('Should handle empty actions array', async () => {
|
|
79
|
+
const stateData = {
|
|
80
|
+
lastBlockNumber: 12345,
|
|
81
|
+
actions: []
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
await adapter.saveState(stateData);
|
|
85
|
+
const loadedActions = await adapter.loadActions();
|
|
86
|
+
|
|
87
|
+
expect(loadedActions).toHaveLength(0);
|
|
88
|
+
expect(Array.isArray(loadedActions)).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('Should handle corrupted action data gracefully', async () => {
|
|
92
|
+
const stateData = {
|
|
93
|
+
lastBlockNumber: 12345,
|
|
94
|
+
actions: [
|
|
95
|
+
{ timeValue: '1m', id: 'valid-action', contractName: 'test', contractMethod: 'test' },
|
|
96
|
+
{ invalid: 'data' }, // Invalid action
|
|
97
|
+
null, // Null action
|
|
98
|
+
'invalid string' // Invalid type
|
|
99
|
+
]
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
await adapter.saveState(stateData);
|
|
103
|
+
const loadedActions = await adapter.loadActions();
|
|
104
|
+
|
|
105
|
+
// Should only load the valid action
|
|
106
|
+
expect(loadedActions).toHaveLength(1);
|
|
107
|
+
expect(loadedActions[0].id).toBe('valid-action');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('Should handle missing state gracefully', async () => {
|
|
111
|
+
const loadedActions = await adapter.loadActions();
|
|
112
|
+
|
|
113
|
+
expect(loadedActions).toHaveLength(0);
|
|
114
|
+
expect(Array.isArray(loadedActions)).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Note: MongoDB tests would require a running MongoDB instance
|
|
119
|
+
// For now, we'll test the serialization logic only
|
|
120
|
+
describe('MongodbAdapter Serialization', () => {
|
|
121
|
+
test('Should serialize actions data correctly for MongoDB', () => {
|
|
122
|
+
const testActions = [
|
|
123
|
+
new TimeAction('1m', 'action1', 'contract1', 'method1', { data: 'test1' }),
|
|
124
|
+
new TimeAction('5m', 'action2', 'contract2', 'method2', { data: 'test2' })
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const serializedActions = testActions.map(a => a.toJSON());
|
|
128
|
+
|
|
129
|
+
const stateData = {
|
|
130
|
+
lastBlockNumber: 12345,
|
|
131
|
+
actions: serializedActions
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Verify the data structure is valid for MongoDB
|
|
135
|
+
expect(stateData.actions).toHaveLength(2);
|
|
136
|
+
expect(stateData.actions[0]).toHaveProperty('timeValue');
|
|
137
|
+
expect(stateData.actions[0]).toHaveProperty('id');
|
|
138
|
+
expect(stateData.actions[0]).toHaveProperty('contractName');
|
|
139
|
+
expect(stateData.actions[0]).toHaveProperty('contractMethod');
|
|
140
|
+
expect(stateData.actions[0]).toHaveProperty('date');
|
|
141
|
+
expect(typeof stateData.actions[0].date).toBe('string'); // Should be ISO string
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|