Skip to content

Commit b2fcabf

Browse files
Completed refactoring, close #1, close #2, close #3
Move credentials to .env file
1 parent 419e7c3 commit b2fcabf

6 files changed

Lines changed: 117 additions & 47 deletions

File tree

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
# database used for testing
3+
TEST_USERNAME="admin"
4+
TEST_PASSWORD="xxx"
5+
TEST_HOST="xxx.sqlitecloud.io"

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,5 @@ dist
119119
lib/
120120

121121
# Mac files
122-
.DS_Store
122+
.DS_Store
123+
.env

package-lock.json

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"lib/**/*"
99
],
1010
"scripts": {
11-
"test": "jest --coverage",
11+
"test": "jest --detectOpenHandles --runInBand",
1212
"test:watch": "jest --watch",
1313
"build": "tsc --project tsconfig.build.json",
1414
"prettier": "prettier --write 'src/**/*'",
@@ -57,6 +57,7 @@
5757
"@types/node": "^12.20.11",
5858
"@typescript-eslint/eslint-plugin": "^4.22.0",
5959
"@typescript-eslint/parser": "^4.22.0",
60+
"dotenv": "^16.0.1",
6061
"eslint": "^7.25.0",
6162
"eslint-config-prettier": "^8.3.0",
6263
"eslint-plugin-node": "^11.1.0",

src/protocol.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const CMD_COMMAND = '^'
2525
const CMD_ARRAY = '='
2626

2727
/** Default timeout value for queries */
28-
export const DEFAULT_TIMEOUT = 30 * 1000
28+
export const DEFAULT_TIMEOUT = 300 * 1000
2929
/** Default tls connection port */
3030
export const DEFAULT_PORT = 9960
3131

@@ -96,7 +96,7 @@ export class SQLiteCloudRowset {
9696
}
9797

9898
/** Configuration for SQLite cloud connection */
99-
export interface SQLiteCloudConfiguration {
99+
export interface SQLiteCloudConfig {
100100
/** Connection string in the form of sqlitecloud://user:password@host:port/database?options */
101101
connectionString?: string
102102

@@ -145,18 +145,16 @@ export interface SQLiteCloudConfiguration {
145145
}
146146

147147
/* SQLiteCloud class */
148-
export default class SQLiteCloudConnection {
148+
export class SQLiteCloudConnection {
149149
/** Connection string if passed */
150150
_connectionString?: string
151-
152151
/** Configuration passed by client or extracted from connection string */
153-
_config: SQLiteCloudConfiguration
154-
152+
_config: SQLiteCloudConfig
155153
/** Currently opened tls socket used to communicated with SQLiteCloud server */
156154
_socket?: tls.TLSSocket
157155

158156
/** Parse and validate provided connectionString or configuration */
159-
constructor(config: SQLiteCloudConfiguration | string, verbose = false) {
157+
constructor(config: SQLiteCloudConfig | string, verbose = false) {
160158
if (typeof config === 'string') {
161159
this._config = this._validateConfiguration({ connectionString: config })
162160
} else {
@@ -172,7 +170,7 @@ export default class SQLiteCloudConnection {
172170
//
173171

174172
/** Validate configuration, apply defaults, throw if something is missing or misconfigured */
175-
private _validateConfiguration(config: SQLiteCloudConfiguration): SQLiteCloudConfiguration {
173+
private _validateConfiguration(config: SQLiteCloudConfig): SQLiteCloudConfig {
176174
if (config.connectionString) {
177175
config = {
178176
...config,
@@ -186,7 +184,7 @@ export default class SQLiteCloudConnection {
186184
config.clientId ||= 'SQLiteCloud'
187185

188186
if (!config.username || !config.password || !config.host) {
189-
throw new SQLiteCloudError('The user, password and host arguments must be specified', { errorCode: 'ERR_MISSING_ARGS' })
187+
throw new SQLiteCloudError('The user, password and host arguments must be specified.', { errorCode: 'ERR_MISSING_ARGS' })
190188
}
191189

192190
return config
@@ -200,9 +198,14 @@ export default class SQLiteCloudConnection {
200198
}
201199

202200
//
203-
// public methods
201+
// public properties
204202
//
205203

204+
/** True if connection is open */
205+
public get connected(): boolean {
206+
return this._socket !== undefined
207+
}
208+
206209
/** Initialization commands sent to database when connection is established */
207210
public get initializationCommands(): string {
208211
const config = this._config
@@ -243,6 +246,10 @@ export default class SQLiteCloudConnection {
243246
return commands
244247
}
245248

249+
//
250+
// public methods
251+
//
252+
246253
/* Opens a connection with the server and sends the initialization commands. Will throw in case of errors. */
247254
public async connect(): Promise<void> {
248255
return new Promise<void>((resolve, reject) => {
@@ -500,14 +507,12 @@ function parseError(buffer: Buffer, spaceIndex: number): never {
500507
const extErrCode = parseInt(extErrCodeStr)
501508
const offsetCode = parseInt(offsetCodeStr)
502509

503-
// Create an Error object and add the custom properties
504-
const scspError = new SQLiteCloudError(errorMessage)
505-
scspError.errorCode = errorCode
506-
scspError.externalErrorCode = extErrCode
507-
scspError.offsetCode = offsetCode
508-
509-
// Throw the custom error
510-
throw scspError
510+
// create an Error object and add the custom properties
511+
throw new SQLiteCloudError(errorMessage, {
512+
errorCode: errorCode.toString(),
513+
externalErrorCode: extErrCode.toString(),
514+
offsetCode
515+
})
511516
}
512517

513518
/** Parse an array of items (each of which will be parsed by type separately) */
@@ -705,7 +710,7 @@ function parseData(data: Buffer | Buffer[]): any {
705710
}
706711

707712
/** Parse connectionString like sqlitecloud://usernam:password@host:port/database?option1=xxx&option2=xxx into its components */
708-
export function parseConnectionString(connectionString: string): SQLiteCloudConfiguration {
713+
export function parseConnectionString(connectionString: string): SQLiteCloudConfig {
709714
try {
710715
// The URL constructor throws a TypeError if the URL is not valid.
711716
const url = new URL(connectionString)

test/protocol.test.ts

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,9 @@
22
* protocol.test.ts - test low level communication protocol
33
*/
44

5-
/* eslint-disable */
5+
import { SQLiteCloudConnection, SQLiteCloudConfig, SQLiteCloudError, parseConnectionString } from '../src/protocol'
66

7-
// https://github.com/jest-community/vscode-jest/blob/master/README.md#autorun
8-
9-
// published library
10-
// var sqlitecloud = require('sqlitecloud-nodejs-sdk')
11-
12-
// code being refactored:
13-
import sqlitecloud, { SQLiteCloudError, parseConnectionString } from '../src/protocol'
14-
15-
const cert = `-----BEGIN CERTIFICATE-----
7+
const TEST_CERTIFICATE = `-----BEGIN CERTIFICATE-----
168
MIID6zCCAtOgAwIBAgIUI0lTm5CfVf3mVP8606CkophcyB4wDQYJKoZIhvcNAQEL
179
BQAwgYQxCzAJBgNVBAYTAklUMQswCQYDVQQIDAJNTjEQMA4GA1UEBwwHVmlhZGFu
1810
YTEbMBkGA1UECgwSU1FMaXRlIENsb3VkLCBJbmMuMRQwEgYDVQQDDAtTUUxpdGVD
@@ -36,26 +28,33 @@ Sjox3HYOoj2uG2669CLAnw6rkHESbi5imasC9FxWBVxWrnNd0icyiDb1wfBc5W9N
3628
otHL5/wB1MaAmCIcQjIxEshj8pSYTecthitmrneimikFf4KFK0YMvGgKrCLmJsg=
3729
-----END CERTIFICATE-----`
3830

39-
const configDev1 = {
40-
clientId: 'dev1.sqlitecloud.io',
41-
username: 'admin', //required unless connectionString is provided
42-
password: 'admin', //required unless connectionString is provided
43-
host: 'dev1.sqlitecloud.io', //required unless connectionString is provided
44-
port: 9960, //required unless connectionString is provided
31+
require('dotenv').config()
32+
33+
const testConfig: SQLiteCloudConfig = {
34+
clientId: 'test',
35+
username: process.env.TEST_USERNAME,
36+
password: process.env.TEST_PASSWORD,
37+
host: process.env.TEST_HOST,
38+
database: process.env.TEST_DATABASE || 'chinook.sqlite',
39+
port: (process.env.TEST_PORT || 9960) as number,
4540
compression: true,
46-
queryTimeout: 10000000,
41+
timeout: 100 * 1000,
4742
tlsOptions: {
48-
ca: cert
43+
ca: TEST_CERTIFICATE
4944
}
5045
}
5146

5247
describe('protocol', () => {
53-
let client: sqlitecloud
48+
let client: SQLiteCloudConnection
5449

5550
beforeEach(async () => {
51+
expect(process.env.TEST_USERNAME).toBeDefined()
52+
expect(process.env.TEST_PASSWORD).toBeDefined()
53+
expect(process.env.TEST_HOST).toBeDefined()
54+
5655
if (!client) {
5756
try {
58-
const connectingClient = new sqlitecloud(configDev1, true)
57+
const connectingClient = new SQLiteCloudConnection(testConfig)
5958
expect(connectingClient).toBeDefined()
6059

6160
await connectingClient.connect()
@@ -80,6 +79,48 @@ describe('protocol', () => {
8079
it('should connect', async () => {
8180
// ...in beforeEach
8281
})
82+
83+
it('should connect with connection string', async () => {
84+
const connectionString = `sqlitecloud://${testConfig.username as string}:${testConfig.password as string}@${testConfig.host as string}`
85+
const connection = new SQLiteCloudConnection({
86+
connectionString,
87+
tlsOptions: {
88+
ca: TEST_CERTIFICATE
89+
}
90+
})
91+
92+
expect(connection).toBeDefined()
93+
await connection.connect()
94+
expect(connection.connected).toBe(true)
95+
await connection.disconnect()
96+
expect(connection.connected).toBe(false)
97+
})
98+
99+
it('should throw when connection string lacks credentials', async () => {
100+
try {
101+
const connectionString = `sqlitecloud://${testConfig.host as string}`
102+
const connection = new SQLiteCloudConnection({
103+
connectionString,
104+
tlsOptions: {
105+
ca: TEST_CERTIFICATE
106+
}
107+
})
108+
109+
expect(connection).toBeDefined()
110+
await connection.connect()
111+
// fail the test if the error is not thrown
112+
expect(true).toBe(false)
113+
} catch (error: any) {
114+
expect(error).toBeDefined()
115+
expect(error).toBeInstanceOf(SQLiteCloudError)
116+
117+
const sqliteCloudError = error as SQLiteCloudError
118+
expect(sqliteCloudError.message).toBe('The user, password and host arguments must be specified.')
119+
expect(sqliteCloudError.errorCode).toBe('ERR_MISSING_ARGS')
120+
expect(sqliteCloudError.externalErrorCode).toBeUndefined()
121+
expect(sqliteCloudError.offsetCode).toBeUndefined()
122+
}
123+
})
83124
})
84125

85126
describe('send test commands', () => {
@@ -162,10 +203,11 @@ describe('protocol', () => {
162203
expect(error).toBeDefined()
163204
expect(error).toBeInstanceOf(SQLiteCloudError)
164205

165-
expect(error.message).toBe('This is a test error message with an extcode and a devil error code.')
166-
expect(error.errorCode).toBe(66666)
167-
expect(error.externalErrorCode).toBe(333)
168-
expect(error.offsetCode).toBe(-1)
206+
const sqliteCloudError = error as SQLiteCloudError
207+
expect(sqliteCloudError.message).toBe('This is a test error message with an extcode and a devil error code.')
208+
expect(sqliteCloudError.errorCode).toBe('66666')
209+
expect(sqliteCloudError.externalErrorCode).toBe('333')
210+
expect(sqliteCloudError.offsetCode).toBe(-1)
169211
}
170212
})
171213

@@ -200,7 +242,7 @@ describe('protocol', () => {
200242

201243
describe('send select commands', () => {
202244
it('should select long formatted string', async () => {
203-
let response = await client.sendCommands("USE DATABASE :memory:; select printf('%.*c', 1000, 'x') AS DDD")
245+
const response = await client.sendCommands("USE DATABASE :memory:; select printf('%.*c', 1000, 'x') AS DDD")
204246
expect(response.numberOfColumns).toBe(1)
205247
expect(response.numberOfRows).toBe(1)
206248
expect(response.version).toBe(1)
@@ -211,7 +253,7 @@ describe('protocol', () => {
211253
})
212254

213255
it('should select database', async () => {
214-
let response = await client.sendCommands('USE DATABASE chinook.sqlite;')
256+
const response = await client.sendCommands('USE DATABASE chinook.sqlite;')
215257
expect(response.numberOfColumns).toBeUndefined()
216258
expect(response.numberOfRows).toBeUndefined()
217259
expect(response.version).toBeUndefined()

0 commit comments

Comments
 (0)