diff --git a/README.md b/README.md index b34d71c..4ede121 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,10 @@ npm install @sqlitecloud/drivers You also have to install Peer Dependencies ```bash -npm install @sqlitecloud/drivers react-native-tcp-socket react-native-fast-base64 +npm install @sqlitecloud/drivers react-native-tcp-socket react-native-quick-base64 ``` -React Native run IOS +React Native run iOS ```bash cd ios && pod install && cd .. && npm run ios @@ -32,7 +32,7 @@ React Native run Android (without ./ in Windows) cd android && ./gradlew clean build && cd .. && npm run android ``` -Expo run IOS +Expo run iOS ```bash npx expo prebuild && npx expo run:ios diff --git a/bun.lockb b/bun.lockb index 82fc869..c48747c 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package-lock.json b/package-lock.json index 63b660e..a65f22e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "@sqlitecloud/drivers", - "version": "1.0.255", + "version": "1.0.274", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@sqlitecloud/drivers", - "version": "1.0.255", + "version": "1.0.274", "license": "MIT", "dependencies": { + "@craftzdog/react-native-buffer": "^6.0.5", "buffer": "^6.0.3", "eventemitter3": "^5.0.1", "jest-html-reporter": "^3.10.2", "lz4js": "^0.2.0", - "react-native-buffer": "^6.0.3", "react-native-url-polyfill": "^2.0.0", "socket.io": "^4.7.5", "socket.io-client": "^4.7.5", @@ -51,7 +51,7 @@ "node": ">=18.0" }, "peerDependencies": { - "react-native": "0.74.5", + "react-native-fast-base64": "*", "react-native-tcp-socket": "^6.2.0" } }, @@ -2444,6 +2444,30 @@ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" }, + "node_modules/@craftzdog/react-native-buffer": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@craftzdog/react-native-buffer/-/react-native-buffer-6.0.5.tgz", + "integrity": "sha512-Av+YqfwA9e7jhgI9GFE/gTpwl/H+dRRLmZyJPOpKTy107j9Oj7oXlm3/YiMNz+C/CEGqcKAOqnXDLs4OL6AAFw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "ieee754": "^1.2.1", + "react-native-quick-base64": "^2.0.5" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -10829,40 +10853,12 @@ } } }, - "node_modules/react-native-buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/react-native-buffer/-/react-native-buffer-6.0.3.tgz", - "integrity": "sha512-zZ0+TytQukFkdq7l9VQ8eypY83qQLMYNNVA+1skHQWcjXWRxsJvR6TAW4/HwD1FTLIySQt7cyvbNYSO30RuNvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "ieee754": "^1.2.1", - "react-native-fast-base64": "^0.1.1" - }, - "peerDependencies": { - "react": "*", - "react-native": "*", - "react-native-fast-base64": "*" - } - }, "node_modules/react-native-fast-base64": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/react-native-fast-base64/-/react-native-fast-base64-0.1.2.tgz", "integrity": "sha512-LBwGGdAQirfU++1r4DX1ufkWKUhY6wwVEbVhyH247YXeubW0jxl7+yl8xlOchz6W/J1KFzXrGTcMiHlv+OpIig==", "license": "MIT", + "peer": true, "engines": { "node": ">= 16.0.0" }, @@ -10871,6 +10867,19 @@ "react-native": "*" } }, + "node_modules/react-native-quick-base64": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/react-native-quick-base64/-/react-native-quick-base64-2.1.2.tgz", + "integrity": "sha512-xghaXpWdB0ji8OwYyo0fWezRroNxiNFCNFpGUIyE7+qc4gA/IGWnysIG5L0MbdoORv8FkTKUvfd6yCUN5R2VFA==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.5.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-tcp-socket": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/react-native-tcp-socket/-/react-native-tcp-socket-6.2.0.tgz", diff --git a/package.json b/package.json index 5774a4c..b772178 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sqlitecloud/drivers", - "version": "1.0.255", + "version": "1.0.276", "description": "SQLiteCloud drivers for Typescript/Javascript in edge, web and node clients", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -43,19 +43,19 @@ }, "homepage": "https://github.com/sqlitecloud/sqlitecloud-js#readme", "dependencies": { + "@craftzdog/react-native-buffer": "^6.0.5", "buffer": "^6.0.3", "eventemitter3": "^5.0.1", "jest-html-reporter": "^3.10.2", "lz4js": "^0.2.0", - "react-native-buffer": "^6.0.3", "react-native-url-polyfill": "^2.0.0", "socket.io": "^4.7.5", "socket.io-client": "^4.7.5", "whatwg-url": "^14.0.0" }, "peerDependencies": { - "react-native-tcp-socket": "^6.2.0", - "react-native-fast-base64": "*" + "react-native-quick-base64": "*", + "react-native-tcp-socket": "^6.2.0" }, "devDependencies": { "@types/bun": "^1.1.1", @@ -101,9 +101,9 @@ "react-native": { "whatwg-url": "react-native-url-polyfill", "tls": "react-native-tcp-socket", - "buffer": "react-native-buffer" + "buffer": "@craftzdog/react-native-buffer" }, "browser": { "tls": false } -} +} \ No newline at end of file diff --git a/test/core/built-in-commands.test.ts b/test/core/built-in-commands.test.ts index cbffa8a..0fdddf0 100644 --- a/test/core/built-in-commands.test.ts +++ b/test/core/built-in-commands.test.ts @@ -2,12 +2,9 @@ * built-in-commands.test.ts - test sqlitecloud built-in commands */ +import { createHash } from 'crypto' import { _, - SQLiteCloudError, - SQLiteCloudRowset, - SQLiteCloudConnection, - SQLiteCloudTlsConnection, CHINOOK_DATABASE_URL, parseconnectionstring, getConnection, @@ -22,7 +19,8 @@ import { colseq, screaming_snake_case, regex_IP_UUID_N, - test + test, + CHINOOK_API_KEY } from './shared' describe.each([ @@ -198,18 +196,19 @@ describe.each([ }) describe.each([ - ['admin', randomName(), randomDate(), true], - ['admin', randomName(), 'WRONG_DATE', false], - ['NOT_EXIST', randomName(), randomDate(), false], - ['admin', '', randomDate(), false] -])('api key', (username, keyName, expiration, ok) => { - let key: string + ['admin', randomName(), randomDate(), _, true], + ['admin', randomName(), randomDate(), randomName(), true], + ['admin', randomName(), 'WRONG_DATE', _, false], + ['NOT_EXIST', randomName(), randomDate(), randomName(), false], + ['admin', '', randomDate(), _, false] +])('api key', (username, keyName, expiration, key, ok) => { + let generated_key: string it(`should${ok ? '' : "n't"} create`, done => { const chinook = getConnection() chinook.sendCommands( - `CREATE APIKEY USER ${username} NAME ${keyName} EXPIRATION "${expiration}"`, - test(done, chinook, ok, /^[a-zA-Z0-9]{43}$/, (res: string) => (key = res)) + `CREATE APIKEY USER ${username} NAME ${keyName} EXPIRATION "${expiration}"${key ? ` KEY ${key}` : ''}`, + test(done, chinook, ok, key ? key : /^[a-zA-Z0-9]{43}$/, (res: string) => (generated_key = res)) ) }) @@ -217,7 +216,7 @@ describe.each([ const chinook = getConnection() chinook.sendCommands( `LIST APIKEYS USER ${username} ${username == 'admin' ? '; LIST MY APIKEYS' : ''}`, - test(done, chinook, ok, { creation_date: date(), expiration_date: expiration, key: key, name: keyName }) + test(done, chinook, ok, { creation_date: date(), expiration_date: expiration, key: generated_key, name: keyName }) ) }) @@ -228,8 +227,8 @@ describe.each([ expiration = randomDate() const chinook = getConnection() chinook.sendCommands( - `SET APIKEY ${key} NAME ${keyName} EXPIRATION "${expiration}"; LIST APIKEYS USER ${username}`, - test(done, chinook, false, { creation_date: date(), expiration_date: prevExpiration, key: key, name: prevKeyName }) + `SET APIKEY ${generated_key} NAME ${keyName} EXPIRATION "${expiration}"; LIST APIKEYS USER ${username}`, + test(done, chinook, false, { creation_date: date(), expiration_date: prevExpiration, key: generated_key, name: prevKeyName }) ) }) @@ -237,20 +236,20 @@ describe.each([ const chinook = getConnection() chinook.sendCommands( `LIST APIKEYS USER ${username} ${username == 'admin' ? '; LIST MY APIKEYS' : ''}`, - test(done, chinook, ok, { creation_date: date(), expiration_date: expiration, key: key, name: keyName }) + test(done, chinook, ok, { creation_date: date(), expiration_date: expiration, key: generated_key, name: keyName }) ) }) it(`should${ok ? '' : "n't"} remove`, done => { const chinook = getConnection() - chinook.sendCommands(`REMOVE APIKEY ${key}`, test(done, chinook, ok)) + chinook.sendCommands(`REMOVE APIKEY ${generated_key}`, test(done, chinook, ok)) }) it(`should${ok ? '' : "n't"} list empty`, done => { const chinook = getConnection() chinook.sendCommands( `LIST APIKEYS USER ${username} ${username == 'admin' ? '; LIST MY APIKEYS' : ''}`, - test(done, chinook, false, { creation_date: date(), expiration_date: expiration, key: key, name: keyName }) + test(done, chinook, false, { creation_date: date(), expiration_date: expiration, key: generated_key, name: keyName }) ) }) }) @@ -351,14 +350,14 @@ describe.each([ }) describe.each([ - [randomName(), randomName(), 'READ', 'chinook.sqlite', 'artists', true], - [randomName(), randomName(), '', '', '', true], - [randomName(), randomName(), 'READ', '', '', true], - [randomName(), randomName(), 'NOT_EXIST', '', '', false], - [randomName(), randomName(), '', 'chinook.sqlite', 'artists', true] + [randomName(), randomName(), 'READ', 'chinook.sqlite', 'artists', CHINOOK_API_KEY, true], + [randomName(), randomName(), '', '', '', CHINOOK_API_KEY, true], + [randomName(), randomName(), 'READ', '', '', CHINOOK_API_KEY, true], + [randomName(), randomName(), 'NOT_EXIST', '', '', randomName(), false], + [randomName(), randomName(), '', 'chinook.sqlite', 'artists', CHINOOK_API_KEY, true] //[randomName(), randomName(), 'READ', 'NOT_EXIST', '', false], //[randomName(), randomName(), 'READ', '', 'NOT_EXIST', false] core not checking if database or table exists -])('user', (username, password, role, database, table, ok) => { +])('user', (username, password, role, database, table, key, ok) => { it(`should${ok ? '' : "n't"} create`, done => { const chinook = getConnection() chinook.sendCommands( @@ -388,6 +387,16 @@ describe.each([ chinook.sendCommands(`AUTH USER ${username} PASSWORD ${password}`, test(done, chinook, ok)) }) + it(`should${ok ? '' : "n't"} auth with apikey`, done => { + const chinook = getConnection() + chinook.sendCommands(`AUTH APIKEY ${key}`, test(done, chinook, ok)) + }) + + it(`should${ok ? '' : "n't"} auth with hash`, done => { + const chinook = getConnection() + chinook.sendCommands(`AUTH USER ${username} HASH ${createHash('sha256').update(password).digest('base64')}`, test(done, chinook, ok)) + }) + it(`should${ok ? '' : "n't"} revoke role`, done => { const gOk = role != '' && ok const chinook = getConnection() @@ -607,14 +616,44 @@ describe.each([ chinook.sendCommands(`CREATE CHANNEL ${name} IF NOT EXISTS`, test(done, chinook, ok)) }) - /* it(`should${ok ? '' : "n't"} listen`, done => { //ERROR Data type: | is not defined in SCSP, it isn't supported yet + it(`should${ok ? '' : "n't"} listen`, done => { + //ERROR Data type: | is not defined in SCSP, it isn't supported yet const chinook = getConnection() - chinook.sendCommands(`LISTEN ${name}`, test(done, chinook, ok)) + chinook.sendCommands( + `LISTEN ${name}`, + test( + done, + chinook, + ok, + /^PAUTH\s([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})\s([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/ + ) + ) }) it(`should${ok ? '' : "n't"} listen table`, done => { const chinook = getConnection() - chinook.sendCommands(`LISTEN TABLE ${table} ${database ? `DATABASE ${database}` : ''}`, test(done, chinook, ok)) - }) */ + chinook.sendCommands( + `LISTEN TABLE ${table} ${database ? `DATABASE ${database}` : ''}`, + test( + done, + chinook, + ok, + /^PAUTH\s([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})\s([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/ + ) + ) + }) + + it.skip(`should${ok ? '' : "n't"} list pubsub connections`, done => { + const chinook = getConnection() + chinook.sendCommands( + `LIST PUBSUB CONNECTIONS`, + test(done, chinook, ok, { + id: expect.any(Number), + dbname: database, + chname: table, + client_uuid: uuid() + }) + ) + }) it(`should${ok ? '' : "n't"} notify`, done => { const chinook = getConnection() @@ -1386,41 +1425,418 @@ describe.each([ }) describe.each([ - ['ID', 'autocheckpoint', true, true, false, true], - ['IP', 'backlog', false, true, false, true], - ['UUID', 'cluster_port', false, false, false, true], - ['MAXROWS', 'newcluster', true, false, true, true], - [undefined, undefined, false, false, false, false], - ['\0\0\\\\', '\0\0\\\\', true, true, true, false], - [99, 99, false, true, false, false] -])('settings', (client_key, cluster_key, detailed, no_read_only, client_editable, ok) => { - it(`should${ok ? '' : "n't"} get client key`, done => { - const chinook = getConnection() - chinook.sendCommands(`GET CLIENT KEY ${client_key}`, test(done, chinook, ok, regex_IP_UUID_N)) + ['COMPRESSION', 1, true], //tofix + ['ID', 10, true], + ['IP', 10, true], + ['MAXDATA', 0, true], //tofix + ['MAXROWS', 0, true], //tofix + ['MAXROWSET', 0, true], //tofix + ['NOBLOB', 0, true], //tofix + ['NONLINEARIZABLE', 0, true], //tofix + ['UUID', 10, true], + ['ZEROTEXT', 0, true], //tofix + [undefined, undefined, false], + ['\0\0\\\\', 10, false], + [99, 10, false], + + //['COMPRESSION', -1 * Number.MAX_VALUE, true], tofix + ['ID', -1 * Number.MAX_VALUE, true], + ['IP', -1 * Number.MAX_VALUE, true], + //['MAXDATA', -1 * Number.MAX_VALUE, true], tofix + //['MAXROWS', -1 * Number.MAX_VALUE, true], tofix + //['MAXROWSET', -1 * Number.MAX_VALUE, true], tofix + //['NOBLOB', -1 * Number.MAX_VALUE, true],/tofix + //['NONLINEARIZABLE', -1 * Number.MAX_VALUE, true], tofix + ['UUID', -1 * Number.MAX_VALUE, true], + //['ZEROTEXT', -1 * Number.MAX_VALUE, true], tofix + [undefined, undefined, false], + ['\0\0\\\\', -1 * Number.MAX_VALUE, false], + [99, -1 * Number.MAX_VALUE, false], + + //['COMPRESSION', Number.MAX_VALUE, true], tofix + ['ID', Number.MAX_VALUE, true], + ['IP', Number.MAX_VALUE, true], + //['MAXDATA', Number.MAX_VALUE, true], tofix + //['MAXROWS', Number.MAX_VALUE, true], tofix + //['MAXROWSET', Number.MAX_VALUE, true], tofix + //['NOBLOB', Number.MAX_VALUE, true], tofix + //['NONLINEARIZABLE', Number.MAX_VALUE, true], tofix + //['UUID', Number.MAX_VALUE, true], + //['ZEROTEXT', Number.MAX_VALUE, true], tofix + [undefined, undefined, false], + ['\0\0\\\\', Number.MAX_VALUE, false], + [99, Number.MAX_VALUE, false], + + //['COMPRESSION', 0, true], tofix + ['ID', 0, true], + ['IP', 0, true], + ['MAXDATA', 0, true], + ['MAXROWS', 0, true], + ['MAXROWSET', 0, true], + ['NOBLOB', 0, true], + ['NONLINEARIZABLE', 0, true], + ['UUID', 0, true], + ['ZEROTEXT', 0, true], + [undefined, undefined, false], + ['\0\0\\\\', 0, false], + [99, 0, false] +])('client settings', (key, value, ok) => { + it(`should${ok ? '' : "n't"} get key`, done => { + const chinook = getConnection() + chinook.sendCommands(`GET CLIENT KEY ${key}`, test(done, chinook, ok, regex_IP_UUID_N)) }) - it(`shouldn't get database key`, done => { + it(`should${ok ? '' : "n't"} list keys`, done => { const chinook = getConnection() - chinook.sendCommands(`GET DATABASE chinook.sqlite KEY ${client_key}`, test(done, chinook, false /* fails everytime in ci */)) + chinook.sendCommands( + `LIST CLIENT KEYS`, + test(done, chinook, ok, { + key: key, + value: expect.stringMatching(regex_IP_UUID_N) + }) + ) }) - it(`should${ok ? '' : "n't"} get cluster key`, done => { + let read_only = false + + it(`should${ok ? '' : "n't"} set key to ${value}`, done => { const chinook = getConnection() - chinook.sendCommands(`GET KEY ${cluster_key}`, test(done, chinook, ok, /[0-9]/)) + chinook.sendCommands(`SET CLIENT KEY ${key} TO ${value}`, (error: any, results: any) => { + if (ok) { + if (results) { + expect(error).toBeNull() + expect(results).toBe('OK') + } else { + expect(error.message).toMatch(/(is read-only|unable to set)/i) + read_only = true + } + done() + chinook.close() + } else { + test(done, chinook, ok)(error, results) + } + }) }) - it(`should${ok ? '' : "n't"} list client keys`, done => { + it(`should${ok ? '' : "n't"} check list keys`, done => { const chinook = getConnection() chinook.sendCommands( `LIST CLIENT KEYS`, test(done, chinook, ok, { - key: client_key, - value: expect.stringMatching(regex_IP_UUID_N) + key: key, + value: !read_only && ok ? value?.toString() : expect.stringMatching(regex_IP_UUID_N) + }) + ) + }) + + it(`should${ok ? '' : "n't"} check key`, done => { + const chinook = getConnection() + chinook.sendCommands(`GET CLIENT KEY ${key}`, test(done, chinook, ok, !read_only && ok ? value?.toString() : regex_IP_UUID_N)) + }) + + it(`should${ok ? '' : "n't"} remove key`, done => { + const chinook = getConnection() + chinook.sendCommands(`REMOVE CLIENT KEY ${key}`, test(done, chinook, ok)) + }) +}) + +describe.each([ + ['hello', 10, true], + [undefined, undefined, false], + ['\0\0\\\\', 10, false], + [99, 10, true], + + ['hello', -1 * Number.MAX_VALUE, true], + [undefined, undefined, false], + ['\0\0\\\\', -1 * Number.MAX_VALUE, false], + [99, -1 * Number.MAX_VALUE, true], + + ['hello', Number.MAX_VALUE, true], + [undefined, undefined, false], + ['\0\0\\\\', Number.MAX_VALUE, false], + [99, Number.MAX_VALUE, true], + + ['hello', 0, true], + [undefined, undefined, false], + ['\0\0\\\\', 0, false], + [99, 0, true] +])('database settings', (key, value, ok) => { + it(`shouldn't get key`, done => { + const chinook = getConnection() + chinook.sendCommands(`GET DATABASE chinook.sqlite KEY ${key}`, test(done, chinook, ok, null)) //key hasn't been set so it's right to receive null + }) + + it(`should${ok ? '' : "n't"} list keys`, done => { + const chinook = getConnection() + chinook.sendCommands( + `LIST DATABASE chinook.sqlite KEYS`, + test(done, chinook, true, { + key: 'backup', + value: '1' }) ) }) - it(`should${ok ? '' : "n't"} list cluster keys`, done => { + it(`should${ok ? '' : "n't"} set key to ${value}`, done => { + const chinook = getConnection() + chinook.sendCommands(`SET DATABASE chinook.sqlite KEY ${key} TO ${value}`, test(done, chinook, ok)) + }) + + it(`should${ok ? '' : "n't"} check key`, done => { + const chinook = getConnection() + chinook.sendCommands(`GET DATABASE chinook.sqlite KEY ${key}`, test(done, chinook, ok, value?.toString())) + }) + + it(`should remove key`, done => { + const chinook = getConnection() + chinook.sendCommands(`REMOVE DATABASE chinook.sqlite KEY ${key}`, test(done, chinook, ok)) + }) +}) + +describe.each([ + ['autocheckpoint', 10, true, true, true], + ['autocheckpoint_full', 10, true, true, true], + ['backlog', 10, false, true, true], + ['backup_node_id', 10, false, true, true], + ['base_path', 10, false, false, true], + ['client_timeout', 10, false, true, true], + ['cluster_address', 10, false, false, true], + ['cluster_config', 10, false, false, true], + ['cluster_node_id', 10, false, false, true], + ['cluster_port', 10, false, false, true], + ['cluster_timeout', 10, false, true, true], + ['command_maxlen', 10, false, true, true], + ['dbbusy_timeout', 10, false, true, true], + ['dbdrop_timeout', 10, false, true, true], + ['dbpage_size', 10, false, true, true], + ['download_chunk_size', 10, false, true, true], + ['follower_client_timeout', 10, false, true, true], + ['insecure', 10, false, true, true], + ['listening_address', 10, false, false, true], + ['listening_port', 10, false, false, true], + ['log_format', 10, false, true, true], + ['log_level', 10, false, true, true], + ['max_chunk_size', 10, false, true, true], + ['max_connections', 10, false, true, true], + ['messages_path', 10, false, false, true], + ['min_compression_size', 10, false, true, true], + ['newcluster', 10, true, false, true], + ['nocluster', 10, false, true, true], + ['nthreads', 10, false, true, true], + ['pubsub_keep_history', 10, false, true, true], + ['pubsub_skip_blob', 10, false, true, true], + ['query_analyzer_enabled', 10, false, true, true], + ['query_analyzer_threshold', 10, false, true, true], + ['raft_election_tick', 10, false, true, true], + ['raft_election_timeout', 10, false, true, true], + ['raft_heartbeat_tick', 10, false, true, true], + ['raft_inc_vacuum_pages', 10, false, true, true], + ['raft_log_level', 10, false, true, true], + ['raft_max_db_size', 10, false, true, true], + ['raft_max_free_size', 10, false, true, true], + ['raft_max_log_entries', 10, false, true, true], + ['raft_tickms', 10, false, true, true], + ['raft_timeout', 10, false, true, true], + ['stats_interval', 10, false, true, true], + ['tcpkeepalive', 10, false, true, true], + ['tcpkeepalive_count', 10, false, true, true], + ['tls_certificate_path', 10, false, false, true], + ['tls_certificatekey_path', 10, false, false, true], + ['tls_cluster_certificate_path', 10, false, false, true], + ['tls_cluster_certificatekey_path', 10, false, false, true], + ['tls_root_certificate_path', 10, false, false, true], + ['tls_verify_client', 10, false, true, true], + ['use_concurrent_transactions', 10, false, true, true], + ['zombie_timeout', 10, false, true, true], + [undefined, undefined, false, false, false], + ['\0\0\\\\', 10, true, true, false], + [99, 10, false, true, false], + + ['autocheckpoint', -1 * Number.MAX_VALUE, true, true, true], + ['autocheckpoint_full', -1 * Number.MAX_VALUE, true, true, true], + ['backlog', -1 * Number.MAX_VALUE, false, true, true], + ['backup_node_id', -1 * Number.MAX_VALUE, false, true, true], + ['base_path', -1 * Number.MAX_VALUE, false, false, true], + ['client_timeout', -1 * Number.MAX_VALUE, false, true, true], + ['cluster_address', -1 * Number.MAX_VALUE, false, false, true], + ['cluster_config', -1 * Number.MAX_VALUE, false, false, true], + ['cluster_node_id', -1 * Number.MAX_VALUE, false, false, true], + ['cluster_port', -1 * Number.MAX_VALUE, false, false, true], + ['cluster_timeout', -1 * Number.MAX_VALUE, false, true, true], + ['command_maxlen', -1 * Number.MAX_VALUE, false, true, true], + ['dbbusy_timeout', -1 * Number.MAX_VALUE, false, true, true], + ['dbdrop_timeout', -1 * Number.MAX_VALUE, false, true, true], + ['dbpage_size', -1 * Number.MAX_VALUE, false, true, true], + ['download_chunk_size', -1 * Number.MAX_VALUE, false, true, true], + ['follower_client_timeout', -1 * Number.MAX_VALUE, false, true, true], + ['insecure', -1 * Number.MAX_VALUE, false, true, true], + ['listening_address', -1 * Number.MAX_VALUE, false, false, true], + ['listening_port', -1 * Number.MAX_VALUE, false, false, true], + ['log_format', -1 * Number.MAX_VALUE, false, true, true], + ['log_level', -1 * Number.MAX_VALUE, false, true, true], + ['max_chunk_size', -1 * Number.MAX_VALUE, false, true, true], + ['max_connections', -1 * Number.MAX_VALUE, false, true, true], + ['messages_path', -1 * Number.MAX_VALUE, false, false, true], + ['min_compression_size', -1 * Number.MAX_VALUE, false, true, true], + ['newcluster', -1 * Number.MAX_VALUE, true, false, true], + ['nocluster', -1 * Number.MAX_VALUE, false, true, true], + ['nthreads', -1 * Number.MAX_VALUE, false, true, true], + ['pubsub_keep_history', -1 * Number.MAX_VALUE, false, true, true], + ['pubsub_skip_blob', -1 * Number.MAX_VALUE, false, true, true], + ['query_analyzer_enabled', -1 * Number.MAX_VALUE, false, true, true], + ['query_analyzer_threshold', -1 * Number.MAX_VALUE, false, true, true], + ['raft_election_tick', -1 * Number.MAX_VALUE, false, true, true], + ['raft_election_timeout', -1 * Number.MAX_VALUE, false, true, true], + ['raft_heartbeat_tick', -1 * Number.MAX_VALUE, false, true, true], + ['raft_inc_vacuum_pages', -1 * Number.MAX_VALUE, false, true, true], + ['raft_log_level', -1 * Number.MAX_VALUE, false, true, true], + ['raft_max_db_size', -1 * Number.MAX_VALUE, false, true, true], + ['raft_max_free_size', -1 * Number.MAX_VALUE, false, true, true], + ['raft_max_log_entries', -1 * Number.MAX_VALUE, false, true, true], + ['raft_tickms', -1 * Number.MAX_VALUE, false, true, true], + ['raft_timeout', -1 * Number.MAX_VALUE, false, true, true], + ['stats_interval', -1 * Number.MAX_VALUE, false, true, true], + ['tcpkeepalive', -1 * Number.MAX_VALUE, false, true, true], + ['tcpkeepalive_count', -1 * Number.MAX_VALUE, false, true, true], + ['tls_certificate_path', -1 * Number.MAX_VALUE, false, false, true], + ['tls_certificatekey_path', -1 * Number.MAX_VALUE, false, false, true], + ['tls_cluster_certificate_path', -1 * Number.MAX_VALUE, false, false, true], + ['tls_cluster_certificatekey_path', -1 * Number.MAX_VALUE, false, false, true], + ['tls_root_certificate_path', -1 * Number.MAX_VALUE, false, false, true], + ['tls_verify_client', -1 * Number.MAX_VALUE, false, true, true], + ['use_concurrent_transactions', -1 * Number.MAX_VALUE, false, true, true], + ['zombie_timeout', -1 * Number.MAX_VALUE, false, true, true], + [undefined, undefined, false, false, false], + ['\0\0\\\\', -1 * Number.MAX_VALUE, true, true, false], + [99, -1 * Number.MAX_VALUE, false, true, false], + + ['autocheckpoint', Number.MAX_VALUE, true, true, true], + ['autocheckpoint_full', Number.MAX_VALUE, true, true, true], + ['backlog', Number.MAX_VALUE, false, true, true], + ['backup_node_id', Number.MAX_VALUE, false, true, true], + ['base_path', Number.MAX_VALUE, false, false, true], + ['client_timeout', Number.MAX_VALUE, false, true, true], + ['cluster_address', Number.MAX_VALUE, false, false, true], + ['cluster_config', Number.MAX_VALUE, false, false, true], + ['cluster_node_id', Number.MAX_VALUE, false, false, true], + ['cluster_port', Number.MAX_VALUE, false, false, true], + ['cluster_timeout', Number.MAX_VALUE, false, true, true], + ['command_maxlen', Number.MAX_VALUE, false, true, true], + ['dbbusy_timeout', Number.MAX_VALUE, false, true, true], + ['dbdrop_timeout', Number.MAX_VALUE, false, true, true], + ['dbpage_size', Number.MAX_VALUE, false, true, true], + ['download_chunk_size', Number.MAX_VALUE, false, true, true], + ['follower_client_timeout', Number.MAX_VALUE, false, true, true], + ['insecure', Number.MAX_VALUE, false, true, true], + ['listening_address', Number.MAX_VALUE, false, false, true], + ['listening_port', Number.MAX_VALUE, false, false, true], + ['log_format', Number.MAX_VALUE, false, true, true], + ['log_level', Number.MAX_VALUE, false, true, true], + ['max_chunk_size', Number.MAX_VALUE, false, true, true], + //['max_connections', Number.MAX_VALUE, false, true, true], tofix + ['messages_path', Number.MAX_VALUE, false, false, true], + ['min_compression_size', Number.MAX_VALUE, false, true, true], + ['newcluster', Number.MAX_VALUE, true, false, true], + ['nocluster', Number.MAX_VALUE, false, true, true], + ['nthreads', Number.MAX_VALUE, false, true, true], + ['pubsub_keep_history', Number.MAX_VALUE, false, true, true], + ['pubsub_skip_blob', Number.MAX_VALUE, false, true, true], + ['query_analyzer_enabled', Number.MAX_VALUE, false, true, true], + ['query_analyzer_threshold', Number.MAX_VALUE, false, true, true], + ['raft_election_tick', Number.MAX_VALUE, false, true, true], + ['raft_election_timeout', Number.MAX_VALUE, false, true, true], + ['raft_heartbeat_tick', Number.MAX_VALUE, false, true, true], + ['raft_inc_vacuum_pages', Number.MAX_VALUE, false, true, true], + ['raft_log_level', Number.MAX_VALUE, false, true, true], + ['raft_max_db_size', Number.MAX_VALUE, false, true, true], + ['raft_max_free_size', Number.MAX_VALUE, false, true, true], + ['raft_max_log_entries', Number.MAX_VALUE, false, true, true], + ['raft_tickms', Number.MAX_VALUE, false, true, true], + ['raft_timeout', Number.MAX_VALUE, false, true, true], + ['stats_interval', Number.MAX_VALUE, false, true, true], + ['tcpkeepalive', Number.MAX_VALUE, false, true, true], + ['tcpkeepalive_count', Number.MAX_VALUE, false, true, true], + ['tls_certificate_path', Number.MAX_VALUE, false, false, true], + ['tls_certificatekey_path', Number.MAX_VALUE, false, false, true], + ['tls_cluster_certificate_path', Number.MAX_VALUE, false, false, true], + ['tls_cluster_certificatekey_path', Number.MAX_VALUE, false, false, true], + ['tls_root_certificate_path', Number.MAX_VALUE, false, false, true], + ['tls_verify_client', Number.MAX_VALUE, false, true, true], + ['use_concurrent_transactions', Number.MAX_VALUE, false, true, true], + ['zombie_timeout', Number.MAX_VALUE, false, true, true], + [undefined, undefined, false, false, false], + ['\0\0\\\\', Number.MAX_VALUE, true, true, false], + [99, Number.MAX_VALUE, false, true, false], + + ['autocheckpoint', 0, true, true, true], + ['autocheckpoint_full', 0, true, true, true], + ['backlog', 0, false, true, true], + ['backup_node_id', 0, false, true, true], + ['base_path', 0, false, false, true], + ['client_timeout', 0, false, true, true], + ['cluster_address', 0, false, false, true], + ['cluster_config', 0, false, false, true], + ['cluster_node_id', 0, false, false, true], + ['cluster_port', 0, false, false, true], + ['cluster_timeout', 0, false, true, true], + ['command_maxlen', 0, false, true, true], + ['dbbusy_timeout', 0, false, true, true], + ['dbdrop_timeout', 0, false, true, true], + ['dbpage_size', 0, false, true, true], + ['download_chunk_size', 0, false, true, true], + ['follower_client_timeout', 0, false, true, true], + ['insecure', 0, false, true, true], + ['listening_address', 0, false, false, true], + ['listening_port', 0, false, false, true], + ['log_format', 0, false, true, true], + ['log_level', 0, false, true, true], + ['max_chunk_size', 0, false, true, true], + ['max_connections', 0, false, true, true], + ['messages_path', 0, false, false, true], + ['min_compression_size', 0, false, true, true], + ['newcluster', 0, true, false, true], + ['nocluster', 0, false, true, true], + ['nthreads', 0, false, true, true], + ['pubsub_keep_history', 0, false, true, true], + ['pubsub_skip_blob', 0, false, true, true], + ['query_analyzer_enabled', 0, false, true, true], + ['query_analyzer_threshold', 0, false, true, true], + ['raft_election_tick', 0, false, true, true], + ['raft_election_timeout', 0, false, true, true], + ['raft_heartbeat_tick', 0, false, true, true], + ['raft_inc_vacuum_pages', 0, false, true, true], + ['raft_log_level', 0, false, true, true], + ['raft_max_db_size', 0, false, true, true], + ['raft_max_free_size', 0, false, true, true], + ['raft_max_log_entries', 0, false, true, true], + //['raft_tickms', 0, false, true, true], tofix + //['raft_timeout', 0, false, true, true], tofix + ['stats_interval', 0, false, true, true], + ['tcpkeepalive', 0, false, true, true], + ['tcpkeepalive_count', 0, false, true, true], + ['tls_certificate_path', 0, false, false, true], + ['tls_certificatekey_path', 0, false, false, true], + ['tls_cluster_certificate_path', 0, false, false, true], + ['tls_cluster_certificatekey_path', 0, false, false, true], + ['tls_root_certificate_path', 0, false, false, true], + ['tls_verify_client', 0, false, true, true], + ['use_concurrent_transactions', 0, false, true, true], + ['zombie_timeout', 0, false, true, true], + [undefined, undefined, false, false, false], + ['\0\0\\\\', 0, true, true, false], + [99, 0, false, true, false] +])('cluster settings', (key, value, detailed, no_read_only, ok) => { + let old_value = expect.stringMatching(/([0-9]|\/|\[)/) + + it(`should${ok ? '' : "n't"} get key`, done => { + const chinook = getConnection() + chinook.sendCommands( + `GET KEY ${key}`, + test(done, chinook, ok, /([0-9]|\/|\[|null)/, (res: any) => (res == null ? (old_value = null) : _)) + ) + }) + + it(`should${ok ? '' : "n't"} list keys`, done => { const chinook = getConnection() chinook.sendCommands( `LIST KEYS${detailed ? ' DETAILED' : ''}${no_read_only ? ' NOREADONLY' : ''}`, @@ -1430,47 +1846,51 @@ describe.each([ ok, detailed ? { - key: cluster_key, + key: key, value: expect.anything(), default_value: no_read_only ? expect.anything() : null, readonly: no_read_only ? 0 : expect.any(Number), description: expect.any(String) } : { - key: cluster_key, - value: expect.anything() + key: key, + value: old_value } ) ) }) - it(`should${ok ? '' : "n't"} set client key`, done => { - const chinook = getConnection() - chinook.sendCommands(`SET CLIENT KEY ${client_key} TO 10`, test(done, chinook, client_editable && ok)) - }) + let read_only = false - it(`should${ok ? '' : "n't"} set database key`, done => { + it(`should${ok ? '' : "n't"} set key to ${value}`, done => { const chinook = getConnection() - chinook.sendCommands(`SET DATABASE chinook.sqlite KEY ${client_key} TO 10`, test(done, chinook, ok)) - }) - - it(`should${ok ? '' : "n't"} set cluster key`, done => { - const chinook = getConnection() - chinook.sendCommands(`SET KEY ${cluster_key} TO 1001`, test(done, chinook, no_read_only ? ok : false)) - }) - - it(`should${ok ? '' : "n't"} remove client key`, done => { - const chinook = getConnection() - chinook.sendCommands(`REMOVE CLIENT KEY ${client_key}`, test(done, chinook, ok)) + chinook.sendCommands(`SET KEY ${key} TO ${value}`, (error: any, results: any) => { + if (ok) { + if (results) { + expect(error).toBeNull() + expect(results).toBe('OK') + } else { + expect(error.message).toMatch(/is read-only/i) + read_only = true + } + done() + chinook.close() + } else { + test(done, chinook, ok)(error, results) + } + }) }) - it(`should remove database key`, done => { + it(`should${ok ? '' : "n't"} check key`, done => { const chinook = getConnection() - chinook.sendCommands(`REMOVE DATABASE chinook.sqlite KEY ${client_key}`, test(done, chinook, ok)) + chinook.sendCommands( + `GET KEY ${key}`, + test(done, chinook, ok, /([0-9]|\/|\[|null)/, (res: any) => (!read_only && ok ? expect(res).toEqual(value?.toString()) : _)) + ) }) - it(`should${ok ? '' : "n't"} remove cluster key`, done => { + it(`should${ok ? '' : "n't"} remove key`, done => { const chinook = getConnection() - chinook.sendCommands(`REMOVE KEY ${cluster_key}`, test(done, chinook, no_read_only ? ok : false)) + chinook.sendCommands(`REMOVE KEY ${key}`, test(done, chinook, !read_only && ok)) }) }) diff --git a/test/core/reserved-commands.test.ts b/test/core/reserved-commands.test.ts index a7bf688..dc3b74b 100644 --- a/test/core/reserved-commands.test.ts +++ b/test/core/reserved-commands.test.ts @@ -2,45 +2,32 @@ * reserved-commands.test.ts - test sqlitecloud reserved commands */ -import { - _, - SQLiteCloudError, - SQLiteCloudRowset, - SQLiteCloudConnection, - SQLiteCloudTlsConnection, - CHINOOK_DATABASE_URL, - parseconnectionstring, - getConnection, - connUsername, - randomName, - randomDate, - randomBool, - date, - ip, - uuid, - bool, - colseq, - screaming_snake_case, - regex_IP_UUID_N, - test -} from './shared' - -describe.skip.each([['example.com', 'chinook.sqlite', 'artists', 3, _, '', true]])('webhook', (url_or_code, database, table, mask, options, secret, ok) => { +import { _, getConnection, test, CHINOOK_API_KEY, date, parseconnectionstring, CHINOOK_DATABASE_URL, uuid, ip } from './shared' + +describe.each([ + ['example.com', 'chinook.sqlite', 'artists', 3, _, 'example', true], + ['example2.com', 'chinook.sqlite', 'artists', 2, _, 'example2', true], + ['example1.com', 'chinook.sqlite', 'artists', 1, _, 'example1', true], + ['example0.com', 'chinook.sqlite', 'artists', 0, _, 'example0', false], + ['example_notexisting.com', _, _, 2, _, 'example_notexisting', false], + ['example.com', 'chinook', 'artist', 3, _, 'example', false], + [_, _, _, 2, _, _, false] +])('webhook', (url_or_code, database, table, mask, options, secret, ok) => { let generated_secret = '' it(`should${ok ? '' : "n't"} add`, done => { const chinook = getConnection() chinook.sendCommands( `ADD WEBHOOK ${url_or_code}`, - test(done, chinook, ok, (r: any) => (generated_secret = r)) + test(done, chinook, ok, /[A-Za-z0-9]{43}/i, (r: any) => (generated_secret = r)) ) }) it(`should${ok ? '' : "n't"} add with secret`, done => { const chinook = getConnection() chinook.sendCommands( - `ADD WEBHOOK ${url_or_code}${database ? `DATABASE ${database}` : ''}${table ? `TABLE ${table}` : ''}${mask ? `MASK ${mask}` : ''}${options ? `OPTIONS ${options}` : ''} SECRET ${secret}`, - test(done, chinook, ok) + `ADD WEBHOOK ${url_or_code}${database ? ` DATABASE ${database}` : ''}${table ? ` TABLE ${table}` : ''}${mask ? ` MASK ${mask}` : ''}${options ? ` OPTIONS ${options}` : ''} SECRET ${secret}`, + test(done, chinook, ok, secret) ) }) @@ -60,38 +47,1176 @@ describe.skip.each([['example.com', 'chinook.sqlite', 'artists', 3, _, '', true] databasename: database, tablename: table, mask: mask, - options: options, + options: options ?? null, secret: secret }, - (r: any) => (id = r[1].id) + (r: any) => (id = r[r.length - 1].id) ) ) }) - it(`should${ok ? '' : "n't"} remove`, done => { + it(`should${ok ? '' : "n't"} remove with secret`, done => { const chinook = getConnection() chinook.sendCommands(`REMOVE WEBHOOK ${id}`, test(done, chinook, ok)) }) - it(`shouldn't list removed`, done => { + it(`shouldn't list ${ok ? 'removed' : ''}`, done => { const chinook = getConnection() chinook.sendCommands( `LIST WEBHOOKS`, - test( - done, - chinook, - false, + test(done, chinook, false, { + id: id, + action: url_or_code, + databasename: database, + tablename: table, + mask: mask, + options: options ?? null, + secret: secret + }) + ) + }) + + it(`should${ok ? '' : "n't"} set`, done => { + const chinook = getConnection() + chinook.sendCommands( + `SET WEBHOOK ${id - 1}${url_or_code ? ` ACTION ${url_or_code}` : ''}${database ? ` DATABASE ${database}` : ''}${table ? ` TABLE ${table}` : ''}${mask ? ` MASK ${mask}` : ''}${options ? ` OPTIONS ${options}` : ''}`, + test(done, chinook, ok) + ) + }) + + it(`should${ok ? '' : "n't"} list set`, done => { + const chinook = getConnection() + chinook.sendCommands( + `LIST WEBHOOKS`, + test(done, chinook, ok, { + id: id - 1, + action: url_or_code, + databasename: database, + tablename: table, + mask: mask, + options: options ?? null, + secret: generated_secret + }) + ) + }) + + it(`should${ok ? '' : "n't"} remove`, done => { + const chinook = getConnection() + chinook.sendCommands(`REMOVE WEBHOOK ${id - 1}`, test(done, chinook, ok)) + }) +}) + +describe.each([ + ['test', true], + [Number.MAX_VALUE, true], + [_, false] +])('debug mask', (mask, ok) => { + it(`should${ok ? '' : "n't"} set`, done => { + const chinook = getConnection() + chinook.sendCommands(`SET DEBUG MASK ${mask ?? ''}`, test(done, chinook, ok)) + }) + + it(`should${ok ? '' : "n't"} remove`, done => { + const chinook = getConnection() + chinook.sendCommands(`REMOVE DEBUG MASK ${mask ?? ''}`, test(done, chinook, ok)) + }) +}) + +describe('command debug', () => { + for (let i = -5; i < 10; i++) { + let ok = false + if (i > 0 && i < 5) ok = true + it(`should${ok ? '' : "n't"} command debug`, done => { + const chinook = getConnection() + chinook.sendCommands( + `COMMAND DEBUG ${i}`, + test( + done, + chinook, + true, + ok + ? { + id: expect.any(Number), + address: ip(), + port: expect.any(Number), + nodeid: expect.any(Number), + username: parseconnectionstring(CHINOOK_DATABASE_URL).username, + database: parseconnectionstring(CHINOOK_DATABASE_URL).database, + uuid: uuid(), + ptr: expect.stringMatching(/^0x[a-z0-9]{12}/), + connection_date: date(), + last_activity: date(), + forward: expect.any(Number), + userid: expect.any(Number), + exp: i != 2 ? undefined : expect.any(Number), + dnow: i == 2 ? undefined : i == 4 ? expect.any(String) : expect.any(Number), + last: i == 2 || i == 4 ? undefined : expect.any(Number), + dlast: i != 4 ? undefined : expect.any(String), + diff: i == 2 ? undefined : expect.any(Number) + } + : 'OK' + ) + ) + }) + } +}) + +describe.skip.each([ + ['test', Number.MAX_VALUE, true], + ['//', '//', false] +])('env', (key, value, ok) => { + it(`should${ok ? '' : "n't"} set`, done => { + const chinook = getConnection() + chinook.sendCommands(`SET ENV ${key} TO ${value}`, test(done, chinook, ok)) + }) + + it(`should${ok ? '' : "n't"} get`, done => { + const chinook = getConnection() + chinook.sendCommands(`GET ENV ${key}`, test(done, chinook, ok, value)) + }) + + it(`should${ok ? '' : "n't"} list`, done => { + const chinook = getConnection() + chinook.sendCommands( + `LIST ENV`, + test(done, chinook, ok, { + key: key, + value: value + }) + ) + }) + + it(`should${ok ? '' : "n't"} remove`, done => { + const chinook = getConnection() + chinook.sendCommands(`REMOVE ENV KEY ${key}`, test(done, chinook, ok)) + }) +}) + +describe.each([ + [true, 2, '192.168.1.1:8860', '192.168.1.1:8860', true] + //[false, 0, '//', '//', false] +])('node', (learner, id, address, cluster, ok) => { + it(`should${ok ? '' : "n't"} add`, done => { + const chinook = getConnection() + chinook.sendCommands(`ADD${learner ? ' LEARNER' : ''} NODE ${id} ADDRESS ${address}${cluster ? ` CLUSTER ${cluster}` : ''}`, test(done, chinook, ok)) + }) + + it(`should${ok ? '' : "n't"} list`, done => { + const chinook = getConnection() + chinook.sendCommands( + `LIST NODES`, + test(done, chinook, ok, { + id: id, + node: address, + cluster: cluster, + status: learner ? 'Learner' : 'Leader', + progress: expect.stringMatching(/(replicate|probe)/i), + match: expect.any(Number), + last_activity: expect.any(String) //date() + }) + ) + }) + + it.skip(`should${ok ? '' : "n't"} auth`, done => { + const chinook = getConnection() + chinook.sendCommands(`AUTH CLUSTER ${id} ${CHINOOK_API_KEY}`, test(done, chinook, ok)) + }) + + it.skip(`should${ok ? '' : "n't"} promote`, done => { + const chinook = getConnection() + chinook.sendCommands(`PROMOTE NODE ${id}`, test(done, chinook, ok)) + }) + + it(`should${ok ? '' : "n't"} remove`, done => { + const chinook = getConnection() + chinook.sendCommands(`REMOVE NODE ${id}`, test(done, chinook, ok)) + }) +}) + +describe('list', () => { + it(`should list compile options`, done => { + const chinook = getConnection() + chinook.sendCommands( + `LIST COMPILE OPTIONS`, + test(done, chinook, true, [ + { compile_options: 'ATOMIC_INTRINSICS=1' }, + { compile_options: 'CODEC=see-aes256-ofb' }, + { compile_options: 'COMPILER=gcc-12.2.0' }, + { compile_options: 'DEFAULT_AUTOVACUUM' }, + { compile_options: 'DEFAULT_CACHE_SIZE=-2000' }, + { compile_options: 'DEFAULT_FILE_FORMAT=4' }, + { compile_options: 'DEFAULT_FOREIGN_KEYS' }, + { compile_options: 'DEFAULT_JOURNAL_SIZE_LIMIT=-1' }, + { compile_options: 'DEFAULT_MMAP_SIZE=0' }, + { compile_options: 'DEFAULT_PAGE_SIZE=4096' }, + { compile_options: 'DEFAULT_PCACHE_INITSZ=20' }, + { compile_options: 'DEFAULT_RECURSIVE_TRIGGERS' }, + { compile_options: 'DEFAULT_SECTOR_SIZE=4096' }, + { compile_options: 'DEFAULT_SYNCHRONOUS=2' }, + { compile_options: 'DEFAULT_WAL_AUTOCHECKPOINT=1000' }, + { compile_options: 'DEFAULT_WAL_SYNCHRONOUS=2' }, + { compile_options: 'DEFAULT_WORKER_THREADS=0' }, + { compile_options: 'DIRECT_OVERFLOW_READ' }, + { compile_options: 'ENABLE_COLUMN_METADATA' }, + { compile_options: 'ENABLE_DBPAGE_VTAB' }, + { compile_options: 'ENABLE_FTS3' }, + { compile_options: 'ENABLE_FTS3_PARENTHESIS' }, + { compile_options: 'ENABLE_FTS3_TOKENIZER' }, + { compile_options: 'ENABLE_FTS4' }, + { compile_options: 'ENABLE_FTS5' }, + { compile_options: 'ENABLE_GEOPOLY' }, + { compile_options: 'ENABLE_LOAD_EXTENSION' }, + { compile_options: 'ENABLE_MATH_FUNCTIONS' }, + { compile_options: 'ENABLE_NORMALIZE' }, + { compile_options: 'ENABLE_PREUPDATE_HOOK' }, + { compile_options: 'ENABLE_RTREE' }, + { compile_options: 'ENABLE_SESSION' }, + { compile_options: 'ENABLE_SNAPSHOT' }, + { compile_options: 'ENABLE_STMT_SCANSTATUS' }, + { compile_options: 'ENABLE_ZIPVFS' }, + { compile_options: 'HAS_CODEC' }, + { compile_options: 'MALLOC_SOFT_LIMIT=1024' }, + { compile_options: 'MAX_ATTACHED=10' }, + { compile_options: 'MAX_COLUMN=2000' }, + { compile_options: 'MAX_COMPOUND_SELECT=500' }, + { compile_options: 'MAX_DEFAULT_PAGE_SIZE=8192' }, + { compile_options: 'MAX_EXPR_DEPTH=1000' }, + { compile_options: 'MAX_FUNCTION_ARG=127' }, + { compile_options: 'MAX_LENGTH=1000000000' }, + { compile_options: 'MAX_LIKE_PATTERN_LENGTH=50000' }, + { compile_options: 'MAX_MMAP_SIZE=0x7fff0000' }, + { compile_options: 'MAX_PAGE_COUNT=0xfffffffe' }, + { compile_options: 'MAX_PAGE_SIZE=65536' }, + { compile_options: 'MAX_SQL_LENGTH=1000000000' }, + { compile_options: 'MAX_TRIGGER_DEPTH=1000' }, + { compile_options: 'MAX_VARIABLE_NUMBER=32766' }, + { compile_options: 'MAX_VDBE_OP=250000000' }, + { compile_options: 'MAX_WORKER_THREADS=8' }, + { compile_options: 'MUTEX_PTHREADS' }, + { compile_options: 'OMIT_AUTOINIT' }, + { compile_options: 'OMIT_DEPRECATED' }, + { compile_options: 'OMIT_TCL_VARIABLE' }, + { compile_options: 'SYSTEM_MALLOC' }, + { compile_options: 'TEMP_STORE=1' }, + { compile_options: 'THREADSAFE=1' } + ]) + ) + }) + + it(`should list only reserved commands`, done => { + const chinook = getConnection() + chinook.sendCommands( + `LIST ONLY RESERVED COMMANDS`, + test(done, chinook, true, [ { - id: expect.any(Number), - action: url_or_code, - databasename: database, - tablename: table, - mask: mask, - options: options, - secret: secret + command: 'ADD WEBHOOK [DATABASE ] [TABLE ] [MASK ] [OPTIONS ]', + count: expect.any(Number), + avgtime: expect.any(Number) }, - (r: any) => (id = r.id) - ) + { + command: 'ADD WEBHOOK [DATABASE ] [TABLE ] [MASK ] [OPTIONS ] SECRET ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'ADD [LEARNER] NODE ADDRESS [CLUSTER ]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { command: 'AUTH APIKEY ', count: expect.any(Number), avgtime: expect.any(Number) }, + { + command: 'AUTH CLUSTER ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'AUTH USER HASH ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'BACKUP FINISH ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'BACKUP INIT [] [SOURCE ]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'BACKUP PAGECOUNT ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'BACKUP REMAINING ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'BACKUP STEP PAGES ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'BLOB BYTES ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'BLOB CLOSE ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'BLOB OPEN TABLE COLUMN ROWID RWFLAG ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'BLOB READ SIZE OFFSET ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'BLOB REOPEN ROWID ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'BLOB WRITE OFFSET DATA ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'COMMAND DEBUG ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'COUNT LOG [FROM ] [TO ] [LEVEL ] [TYPE ] [ID] [NODE ]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'CREATE APIKEY USER NAME [EXPIRATION ] KEY ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'DATABASE CACHEFLUSH', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { command: 'DATABASE ERRNO', count: expect.any(Number), avgtime: expect.any(Number) }, + { + command: 'DATABASE FILENAME ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'DATABASE GET CHANGES', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'DATABASE GET ROWID', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'DATABASE GET TOTAL CHANGES', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'DATABASE LIMIT [VALUE ]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'DATABASE NAME ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'DATABASE READONLY ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'DATABASE STATUS RESET ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'DATABASE TXNSTATE []', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { command: 'DOWNLOAD ABORT', count: expect.any(Number), avgtime: expect.any(Number) }, + { + command: 'DOWNLOAD DATABASE [IF EXISTS]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { command: 'DOWNLOAD STEP', count: expect.any(Number), avgtime: expect.any(Number) }, + { + command: 'GET CONNECTION STATUS', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { command: 'HELP <>', count: expect.any(Number), avgtime: expect.any(Number) }, + { + command: 'LIST COMPILE OPTIONS', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'LIST LATENCY KEY [NODE ]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'LIST LATENCY [NODE ]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { command: 'LIST WEBHOOKS', count: expect.any(Number), avgtime: expect.any(Number) }, + { + command: 'LIST [ONLY] RESERVED COMMANDS [DETAILED]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'LOAD DATABASE [KEY ]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'PAUTH ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'PROMOTE NODE ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { command: 'PUBSUB ONLY', count: expect.any(Number), avgtime: expect.any(Number) }, + { command: 'QUIT SERVER', count: expect.any(Number), avgtime: expect.any(Number) }, + { + command: 'REMOVE DEBUG MASK ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'REMOVE NODE ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'REMOVE WEBHOOK ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'RESERVE DATABASE UUID ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'SET DEBUG MASK ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'SET USER ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'SET WEBHOOK [ACTION ] [DATABASE ] [TABLE ] [MASK ] [OPTIONS ]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'SQLITE RANDOMNESS [RESET]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'SQLITE STATUS RESET ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'SWITCH APIKEY ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'SWITCH DATABASE ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'SWITCH USER ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'TRANSFER DATABASE [KEY ] [SNAPSHOT ] [INTERNAL]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'UNRESERVE DATABASE [UUID]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { command: 'UPLOAD ABORT', count: expect.any(Number), avgtime: expect.any(Number) }, + { + command: 'UPLOAD DATABASE [KEY ] [REPLACE]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'VERIFY USER PASSWORD [IP ]', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'VM BIND TYPE COLUMN VALUE ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'VM CLEAR ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { command: 'VM COMPILE ', count: expect.any(Number), avgtime: expect.any(Number) }, + { command: 'VM EXECUTE ', count: expect.any(Number), avgtime: expect.any(Number) }, + { + command: 'VM FINALIZE ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { command: 'VM LIST ', count: expect.any(Number), avgtime: expect.any(Number) }, + { + command: 'VM PARAMETER INDEX ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'VM PARAMETER NAME ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'VM RESET ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'VM SCAN STATUS INDEX OP ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'VM SCAN STATUS RESET ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { command: 'VM SQL ', count: expect.any(Number), avgtime: expect.any(Number) }, + { + command: 'VM STATUS OP RESET ', + count: expect.any(Number), + avgtime: expect.any(Number) + }, + { + command: 'VM STEP []', + count: expect.any(Number), + avgtime: expect.any(Number) + } + ]) + ) + }) + + it(`should list only reserved commands detailed`, done => { + const chinook = getConnection() + chinook.sendCommands( + `LIST ONLY RESERVED COMMANDS DETAILED`, + test(done, chinook, true, [ + { + command: 'ADD WEBHOOK [DATABASE ] [TABLE ] [MASK ] [OPTIONS ]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'ADD WEBHOOK [DATABASE ] [TABLE ] [MASK ] [OPTIONS ] SECRET ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'ADD [LEARNER] NODE ADDRESS [CLUSTER ]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { command: 'AUTH APIKEY ', count: expect.any(Number), avgtime: expect.any(Number), privileges: expect.anything() }, + { + command: 'AUTH CLUSTER ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'AUTH USER HASH ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'BACKUP FINISH ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'BACKUP INIT [] [SOURCE ]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'BACKUP PAGECOUNT ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'BACKUP REMAINING ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'BACKUP STEP PAGES ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'BLOB BYTES ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'BLOB CLOSE ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'BLOB OPEN TABLE COLUMN ROWID RWFLAG ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'BLOB READ SIZE OFFSET ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'BLOB REOPEN ROWID ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'BLOB WRITE OFFSET DATA ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'COMMAND DEBUG ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'COUNT LOG [FROM ] [TO ] [LEVEL ] [TYPE ] [ID] [NODE ]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'CREATE APIKEY USER NAME [EXPIRATION ] KEY ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'DATABASE CACHEFLUSH', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { command: 'DATABASE ERRNO', count: expect.any(Number), avgtime: expect.any(Number), privileges: expect.anything() }, + { + command: 'DATABASE FILENAME ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'DATABASE GET CHANGES', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'DATABASE GET ROWID', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'DATABASE GET TOTAL CHANGES', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'DATABASE LIMIT [VALUE ]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'DATABASE NAME ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'DATABASE READONLY ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'DATABASE STATUS RESET ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'DATABASE TXNSTATE []', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { command: 'DOWNLOAD ABORT', count: expect.any(Number), avgtime: expect.any(Number), privileges: expect.anything() }, + { + command: 'DOWNLOAD DATABASE [IF EXISTS]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { command: 'DOWNLOAD STEP', count: expect.any(Number), avgtime: expect.any(Number), privileges: expect.anything() }, + { + command: 'GET CONNECTION STATUS', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { command: 'HELP <>', count: expect.any(Number), avgtime: expect.any(Number), privileges: expect.anything() }, + { + command: 'LIST COMPILE OPTIONS', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'LIST LATENCY KEY [NODE ]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'LIST LATENCY [NODE ]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { command: 'LIST WEBHOOKS', count: expect.any(Number), avgtime: expect.any(Number), privileges: expect.anything() }, + { + command: 'LIST [ONLY] RESERVED COMMANDS [DETAILED]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'LOAD DATABASE [KEY ]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'PAUTH ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'PROMOTE NODE ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { command: 'PUBSUB ONLY', count: expect.any(Number), avgtime: expect.any(Number), privileges: expect.anything() }, + { command: 'QUIT SERVER', count: expect.any(Number), avgtime: expect.any(Number), privileges: expect.anything() }, + { + command: 'REMOVE DEBUG MASK ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'REMOVE NODE ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'REMOVE WEBHOOK ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'RESERVE DATABASE UUID ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'SET DEBUG MASK ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'SET USER ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'SET WEBHOOK [ACTION ] [DATABASE ] [TABLE ] [MASK ] [OPTIONS ]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'SQLITE RANDOMNESS [RESET]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'SQLITE STATUS RESET ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'SWITCH APIKEY ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'SWITCH DATABASE ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'SWITCH USER ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'TRANSFER DATABASE [KEY ] [SNAPSHOT ] [INTERNAL]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'UNRESERVE DATABASE [UUID]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { command: 'UPLOAD ABORT', count: expect.any(Number), avgtime: expect.any(Number), privileges: expect.anything() }, + { + command: 'UPLOAD DATABASE [KEY ] [REPLACE]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'VERIFY USER PASSWORD [IP ]', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'VM BIND TYPE COLUMN VALUE ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'VM CLEAR ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { command: 'VM COMPILE ', count: expect.any(Number), avgtime: expect.any(Number), privileges: expect.anything() }, + { command: 'VM EXECUTE ', count: expect.any(Number), avgtime: expect.any(Number), privileges: expect.anything() }, + { + command: 'VM FINALIZE ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { command: 'VM LIST ', count: expect.any(Number), avgtime: expect.any(Number), privileges: expect.anything() }, + { + command: 'VM PARAMETER INDEX ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'VM PARAMETER NAME ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'VM RESET ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'VM SCAN STATUS INDEX OP ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'VM SCAN STATUS RESET ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { command: 'VM SQL ', count: expect.any(Number), avgtime: expect.any(Number), privileges: expect.anything() }, + { + command: 'VM STATUS OP RESET ', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + }, + { + command: 'VM STEP []', + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.anything() + } + ]) + ) + }) + + it(`should list reserved commands`, done => { + const chinook = getConnection() + chinook.sendCommands( + `LIST RESERVED COMMANDS`, + test(done, chinook, true, { + command: expect.any(String), + count: expect.any(Number), + avgtime: expect.any(Number) + }) + ) + }) + + it(`should list reserved commands detailed`, done => { + const chinook = getConnection() + chinook.sendCommands( + `LIST RESERVED COMMANDS DETAILED`, + test(done, chinook, true, { + command: expect.any(String), + count: expect.any(Number), + avgtime: expect.any(Number), + privileges: expect.any(String) + }) + ) + }) + + it(`should get help`, done => { + //to be implemented + const chinook = getConnection() + chinook.sendCommands(`HELP <>`, test(done, chinook, true)) + }) +}) + +describe.each([[true]])('connection', ok => { + it(`should${ok ? '' : "n't"} get connection status`, done => { + const chinook = getConnection() + chinook.sendCommands( + `GET CONNECTION STATUS`, + test(done, chinook, ok, [parseconnectionstring(CHINOOK_DATABASE_URL).username, parseconnectionstring(CHINOOK_DATABASE_URL).database, 0, 0]) + ) + }) +}) + +describe.each([ + ['chinook.sqlite', true], + [_, false] +])('download database', (database, ok) => { + it(`should${ok ? '' : "n't"} download database`, done => { + const chinook = getConnection() + chinook.sendCommands( + `DOWNLOAD DATABASE ${database}`, + ok + ? (e, r) => { + expect(e).toBeNull() + expect(r).toEqual([expect.any(Number), expect.any(Number), expect.any(Number)]) //could fail?? + r.forEach((v: number) => expect(v).toBeGreaterThan(0)) + for (let i = 0; i < r.length - 1; i++) { + chinook.sendCommands( + `DOWNLOAD STEP`, + i != r.length - 2 + ? (e, r) => { + expect(e).toBeNull() + expect(r).toBeInstanceOf(Buffer) + } + : test(done, chinook, ok, expect.any(Buffer)) + ) + } + } + : test(done, chinook, ok) + ) + }) + + it(`should${ok ? '' : "n't"} abort download database`, done => { + const chinook = getConnection() + chinook.sendCommands( + `DOWNLOAD DATABASE ${database}`, + ok + ? (e, r) => { + expect(e).toBeNull() + expect(r).toEqual([expect.any(Number), expect.any(Number), expect.any(Number)]) + r.forEach((v: number) => expect(v).toBeGreaterThan(0)) + + chinook.sendCommands(`DOWNLOAD STEP`, (e, r) => { + expect(e).toBeNull() + expect(r).toBeInstanceOf(Buffer) + chinook.sendCommands(`DOWNLOAD ABORT`, (e, r) => { + expect(e).toBeNull() + expect(r).toEqual('OK') + chinook.sendCommands(`DOWNLOAD STEP`, test(done, chinook, ok)) + }) + }) + } + : test(done, chinook, ok) + ) + }) + + it(`should download database if exists${ok ? '' : " (it doesn't exist)"}`, done => { + const chinook = getConnection() + chinook.sendCommands( + `DOWNLOAD DATABASE ${database} IF EXISTS`, + ok + ? (e, r) => { + expect(e).toBeNull() + expect(r).toEqual([expect.any(Number), expect.any(Number), expect.any(Number)]) + r.forEach((v: number) => expect(v).toBeGreaterThan(0)) + for (let i = 0; i < r.length - 1; i++) { + chinook.sendCommands( + `DOWNLOAD STEP`, + i != r.length - 2 + ? (e, r) => { + expect(e).toBeNull() + expect(r).toBeInstanceOf(Buffer) + } + : test(done, chinook, ok, expect.any(Buffer)) + ) + } + } + : (e, r) => { + expect(e).toBeNull() + expect(r).toEqual([0, 0, expect.any(Number)]) + chinook.sendCommands(`DOWNLOAD STEP`, test(done, chinook, ok)) + } ) }) }) + +describe.each([[true]])('database commands', ok => { + it(`should${ok ? '' : "n't"} do a cache flush`, done => { + const chinook = getConnection() + chinook.sendCommands(`DATABASE CACHEFLUSH`, test(done, chinook, ok)) + }) + + it(`should${ok ? '' : "n't"} get error number`, done => { + const chinook = getConnection() + chinook.sendCommands(`DATABASE ERRNO`, test(done, chinook, ok, 0)) + }) + + it(`should${ok ? '' : "n't"} get changes`, done => { + const chinook = getConnection() + chinook.sendCommands(`DATABASE GET CHANGES`, test(done, chinook, ok, 0)) + }) + + it(`should${ok ? '' : "n't"} get rowid`, done => { + const chinook = getConnection() + chinook.sendCommands(`DATABASE GET ROWID`, test(done, chinook, ok, 0)) + }) + + it(`should${ok ? '' : "n't"} get total changes`, done => { + const chinook = getConnection() + chinook.sendCommands(`DATABASE GET TOTAL CHANGES`, test(done, chinook, ok, 0)) + }) +}) + +describe('pubsub', () => { + it(`should do pubsub only`, done => { + const chinook = getConnection() + chinook.sendCommands(`PUBSUB ONLY`, (e, r) => { + expect(e).toBeNull() + expect(r).toEqual('OK') + chinook.sendCommands(`LIST DATABASES`, test(done, chinook, false)) + }) + }) +}) + +/* describe.only('sqlite commands', () => { + it(`should set sqlite randomness`, done => { + const chinook = getConnection() + chinook.sendCommands(``, ) + }) +}) */ diff --git a/test/core/shared.ts b/test/core/shared.ts index 0bd5101..839a982 100644 --- a/test/core/shared.ts +++ b/test/core/shared.ts @@ -1,7 +1,7 @@ import { SQLiteCloudError, SQLiteCloudRowset } from '../../src/index' import { SQLiteCloudConnection } from '../../src/drivers/connection' import { SQLiteCloudTlsConnection } from '../../src/drivers/connection-tls' -import { CHINOOK_DATABASE_URL } from '../shared' +import { CHINOOK_DATABASE_URL, CHINOOK_API_KEY } from '../shared' import { parseconnectionstring } from '../../src/drivers/utilities' const _ = undefined // to use undefined as empty argument @@ -79,7 +79,11 @@ const test = (done: jest.DoneCallback, chinook: SQLiteCloudConnection, ok: boole expect(results).toBe(expectedResult) } } else { - expect(results).toBe(expectedResult) + if (expectedResult && expectedResult.source.includes('null')) { + expect(results).toBeNull() + } else { + expect(results).toBe(expectedResult) + } } } else { try { @@ -91,7 +95,7 @@ const test = (done: jest.DoneCallback, chinook: SQLiteCloudConnection, ok: boole try { expect(error).toBeInstanceOf(SQLiteCloudError) expect((error as SQLiteCloudError).message).toMatch( - /(not found|doesn\'t exist|does not exist|invalid|unable|fail|cannot|must be unique|unknown|undefined|error|no such|not available|try again later|wrong|has no|is read-only)/i + /(not found|doesn\'t exist|does not exist|invalid|unable|fail|cannot|must be unique|unknown|undefined|error|no such|not available|try again later|wrong|has no|is read-only|ended the connection)/i ) expect(results).toBeUndefined() } catch { @@ -113,11 +117,8 @@ const test = (done: jest.DoneCallback, chinook: SQLiteCloudConnection, ok: boole export { _, - SQLiteCloudError, - SQLiteCloudRowset, - SQLiteCloudConnection, - SQLiteCloudTlsConnection, CHINOOK_DATABASE_URL, + CHINOOK_API_KEY, parseconnectionstring, getConnection, connUsername,