Skip to content

Commit cebacee

Browse files
Merge pull request #20 from oslabs-beta/gb/customerror
Gb/customerror
2 parents 8d0b151 + f39e68e commit cebacee

File tree

6 files changed

+285
-175
lines changed

6 files changed

+285
-175
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,4 @@ dist
104104
.tern-port
105105

106106
# Local
107-
./ksqljsTest.js
107+
./ksqljsTest.js

__tests__/integrationtests.js

Lines changed: 105 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
const ksqljs = require('../ksqljs/ksqlJS.js');
22

33
// Pre-requisite: start a docker container
4+
/* To add to README: Prior to running test with 'npm test', please start the ksqlDB
5+
server using the command 'docker compose-up'. This will spin up a ksqlDB server on
6+
'http://localhost:8088'
7+
*/
48

59
describe('--Integration Tests--', () => {
610

@@ -27,7 +31,7 @@ describe('--Integration Tests--', () => {
2731
}
2832
expect(streamExists).toEqual(true);
2933
})
30-
34+
3135
it('.push properly creates a push query', () => {
3236
let pushActive = false;
3337
client.push('SELECT * FROM TESTJESTSTREAM EMIT CHANGES LIMIT 1;', async (data) => {
@@ -37,29 +41,29 @@ describe('--Integration Tests--', () => {
3741
expect(pushActive).toEqual(true)
3842
});
3943
})
40-
44+
4145
it('.terminate properly terminates a push query', () => {
4246
client.push('SELECT * FROM TESTJESTSTREAM EMIT CHANGES LIMIT 3;', async (data) => {
4347
const terminateRes = await client.terminate(JSON.parse(data).queryId);
4448
expect(terminateRes.wasTerminated).toEqual(true);
4549
})
4650
})
47-
51+
4852
it('.insertStream properly inserts a row into a stream', async () => {
4953
const response = await client.insertStream('TESTJESTSTREAM', [
5054
{ "name": "stab-rabbit", "email": "123@mail.com", "age": 100 }
5155
]);
5256
console.log(response);
5357
const data = [];
54-
await client.push('SELECT * FROM TESTJESTSTREAM;', async (chunk) => {
58+
await client.push('SELECT * FROM TESTJESTSTREAM EMIT CHANGES;', async (chunk) => {
5559
data.push(JSON.parse(chunk));
5660
if (data[1]) {
5761
client.terminate(data[0].queryId);
5862
expect(data[1]).toEqual(["stab-rabbit", "123@mail.com", 100])
5963
}
6064
});
6165
})
62-
66+
6367
it('.pull receives the correct data from a pull query', async () => {
6468
const pullData = await client.pull("SELECT * FROM TESTJESTSTREAM;");
6569
expect(pullData[1]).toEqual(['stab-rabbit', '123@mail.com', 100]);
@@ -73,20 +77,25 @@ describe('--Integration Tests--', () => {
7377
expect(expectPullData).toEqual(expectData);
7478
})
7579
})
76-
7780
describe('--Health Tests--', () => {
7881
beforeAll((done) => {
7982
client = new ksqljs({ ksqldbURL: 'http://localhost:8088' });
8083
done();
8184
});
82-
83-
afterAll(async () => {
84-
await client.ksql('DROP STREAM IF EXISTS TESTSTREAM2;');
85-
})
8685

87-
it('.inspectQueryStatus checks if a stream is created successfully', async () => {
88-
const streamName = 'TESTSTREAM2'
89-
const create = await client.ksql(`CREATE STREAM IF NOT EXISTS ${streamName}
86+
describe('--Health Tests--', () => {
87+
beforeAll((done) => {
88+
client = new ksqljs({ ksqldbURL: 'http://localhost:8088' });
89+
done();
90+
});
91+
92+
afterAll(async () => {
93+
await client.ksql('DROP STREAM IF EXISTS TESTSTREAM2;');
94+
})
95+
96+
it('.inspectQueryStatus checks if a stream is created successfully', async () => {
97+
const streamName = 'TESTSTREAM2'
98+
const create = await client.ksql(`CREATE STREAM IF NOT EXISTS ${streamName}
9099
(name VARCHAR,
91100
email varchar,
92101
age INTEGER)
@@ -95,92 +104,93 @@ describe('--Integration Tests--', () => {
95104
VALUE_FORMAT = 'json',
96105
PARTITIONS = 1
97106
);`);
98-
const commandId = create ? create.commandId : `stream/${streamName}/create`;
99-
const status = await client.inspectQueryStatus(commandId);
100-
// response should be { status: 'SUCCESS', message: 'Stream created', queryId: null }
101-
expect(status.data).toEqual(expect.objectContaining({
102-
status: expect.any(String),
103-
message: expect.any(String),
104-
queryId: null
105-
}));
106-
})
107-
108-
it('.inspectServerInfo returns the server info and status', async () => {
109-
const status = await client.inspectServerInfo();
110-
// should return something like: {
111-
// KsqlServerInfo: {
112-
// version: '0.25.1',
113-
// kafkaClusterId: '0Yxd6N5OSKGDUalltPWvXg',
114-
// ksqlServiceId: 'default_',
115-
// serverStatus: 'RUNNING'
116-
// }
117-
// }
118-
expect(status.data).toEqual(expect.objectContaining({
119-
KsqlServerInfo: expect.objectContaining({
120-
version: expect.any(String),
121-
kafkaClusterId: expect.any(String),
122-
serverStatus: expect.any(String)
107+
const commandId = create ? create.commandId : `stream/${streamName}/create`;
108+
const status = await client.inspectQueryStatus(commandId);
109+
// response should be { status: 'SUCCESS', message: 'Stream created', queryId: null }
110+
expect(status.data).toEqual(expect.objectContaining({
111+
status: expect.any(String),
112+
message: expect.any(String),
113+
queryId: null
114+
}));
115+
})
116+
117+
it('.inspectServerInfo returns the server info and status', async () => {
118+
const status = await client.inspectServerInfo();
119+
// should return something like: {
120+
// KsqlServerInfo: {
121+
// version: '0.25.1',
122+
// kafkaClusterId: '0Yxd6N5OSKGDUalltPWvXg',
123+
// ksqlServiceId: 'default_',
124+
// serverStatus: 'RUNNING'
125+
// }
126+
// }
127+
expect(status.data).toEqual(expect.objectContaining({
128+
KsqlServerInfo: expect.objectContaining({
129+
version: expect.any(String),
130+
kafkaClusterId: expect.any(String),
131+
serverStatus: expect.any(String)
132+
})
133+
}));
134+
})
135+
136+
it('.inspectServerHealth returns the server health', async () => {
137+
const status = await client.inspectServerHealth();
138+
// should return something like: {
139+
// isHealthy: true,
140+
// details: {
141+
// metastore: { isHealthy: true },
142+
// kafka: { isHealthy: true },
143+
// commandRunner: { isHealthy: true }
144+
// }
145+
// }
146+
expect(status.data).toEqual(expect.objectContaining({
147+
isHealthy: expect.any(Boolean),
148+
details: expect.objectContaining({
149+
metastore: expect.anything(),
150+
kafka: expect.anything(),
151+
commandRunner: expect.anything()
152+
})
123153
})
124-
}));
125-
})
126-
127-
it('.inspectServerHealth returns the server health', async () => {
128-
const status = await client.inspectServerHealth();
129-
// should return something like: {
130-
// isHealthy: true,
131-
// details: {
132-
// metastore: { isHealthy: true },
133-
// kafka: { isHealthy: true },
134-
// commandRunner: { isHealthy: true }
135-
// }
136-
// }
137-
expect(status.data).toEqual(expect.objectContaining({
138-
isHealthy: expect.any(Boolean),
139-
details: expect.objectContaining({
140-
metastore: expect.anything(),
141-
kafka: expect.anything(),
142-
commandRunner: expect.anything()
154+
);
155+
})
156+
157+
it('.inspectClusterStatus returns the cluster status', async () => {
158+
const status = await client.inspectClusterStatus();
159+
// should return something like: {
160+
// clusterStatus: {
161+
// 'ksqldb-server:8088': {
162+
// hostAlive: true,
163+
// lastStatusUpdateMs: 1653164479237,
164+
// activeStandbyPerQuery: [Object],
165+
// hostStoreLags: [Object]
166+
// }
167+
// }}
168+
expect(status.data).toEqual(expect.objectContaining({
169+
clusterStatus: expect.anything()
143170
})
171+
);
144172
})
145-
);
146-
})
147-
148-
it('.inspectClusterStatus returns the cluster status', async () => {
149-
const status = await client.inspectClusterStatus();
150-
// should return something like: {
151-
// clusterStatus: {
152-
// 'ksqldb-server:8088': {
153-
// hostAlive: true,
154-
// lastStatusUpdateMs: 1653164479237,
155-
// activeStandbyPerQuery: [Object],
156-
// hostStoreLags: [Object]
157-
// }
158-
// }}
159-
expect(status.data).toEqual(expect.objectContaining({
160-
clusterStatus: expect.anything()
173+
174+
it('.isValidProperty returns true if a server configuration property is not prohibited from setting', async () => {
175+
const status = await client.isValidProperty('test');
176+
// should return true
177+
expect(status.data).toEqual(true);
161178
})
162-
);
163-
})
164-
165-
it('.isValidProperty returns true if a server configuration property is not prohibited from setting', async () => {
166-
const status = await client.isValidProperty('test');
167-
// should return true
168-
expect(status.data).toEqual(true);
179+
180+
// it('isValidProperty returns an error if the server property is prohibited from setting', async () => {
181+
// const status = await client.isValidProperty('ksql.connect.url');
182+
// // should return something like
183+
// // {
184+
// // "@type": "generic_error",
185+
// // "error_code": 40000,
186+
// // "message": "One or more properties overrides set locally are prohibited by the KSQL server (use UNSET to reset their default value): [ksql.service.id]"
187+
// // }
188+
// expect(status.data).toEqual(expect.objectContaining({
189+
// type: expect.any(String),
190+
// error_code: expect.any(Number),
191+
// message: expect.any(String),
192+
// }));
193+
// })
169194
})
170-
171-
// it('isValidProperty returns an error if the server property is prohibited from setting', async () => {
172-
// const status = await client.isValidProperty('ksql.connect.url');
173-
// // should return something like
174-
// // {
175-
// // "@type": "generic_error",
176-
// // "error_code": 40000,
177-
// // "message": "One or more properties overrides set locally are prohibited by the KSQL server (use UNSET to reset their default value): [ksql.service.id]"
178-
// // }
179-
// expect(status.data).toEqual(expect.objectContaining({
180-
// type: expect.any(String),
181-
// error_code: expect.any(Number),
182-
// message: expect.any(String),
183-
// }));
184-
// })
185195
})
186196
})

__tests__/builderTest.js renamed to __tests__/queryBuilderTests.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
const queryBuilder = require('../ksqljs/queryBuilder.js');
2+
const { QueryBuilderError, EmptyQueryError, NumParamsError, InappropriateStringParamError } = require('../ksqljs/customErrors.js');
3+
24

35
describe('Unit tests for query builder class', () => {
46
let builder;
@@ -18,33 +20,34 @@ describe('Unit tests for query builder class', () => {
1820
});
1921

2022
describe('Error testing', () => {
21-
it('returns an error if number of params is different from number of question marks', () => {
22-
const finishedQuery = builder.build(query, 123, "middle", "extra");
23-
expect(finishedQuery).toBeInstanceOf(Error);
23+
it('throws an error if number of params is different from number of question marks', () => {
24+
expect(() => { builder.build(query, 123, "middle", "extra") }).toThrow(NumParamsError);
2425
});
2526

26-
it('returns an error if the query is empty', () => {
27+
it('throws an error if the query is empty', () => {
2728
const query = '';
28-
const finishedQuery = builder.build(query, 123);
29-
expect(finishedQuery).toBeInstanceOf(Error);
29+
expect(() => { builder.build(query, 123) }).toThrow(EmptyQueryError);
3030
});
3131

32-
it('returns an error if an object is passed in as a param', () => {
33-
const finishedQuery = builder.build(query, 123, { "middle": "size" });
34-
expect(finishedQuery).toBeInstanceOf(Error);
32+
it('throws an error if an object is passed in as a param', () => {
33+
expect(() => { builder.build(query, 123, { "middle": "size" }) }).toThrow(QueryBuilderError);
3534
});
3635
});
3736

3837
describe('SQL injection', () => {
3938
it("prevents 'OR 1=1' SQL injection by escaping single quotations ", () => {
4039
// https://stackoverflow.com/questions/5139770/escape-character-in-sql-server
41-
const finishedQuery = builder.build(query, 123, "middle' OR 1=1",);
40+
const finishedQuery = builder.build(query, 123, "middle' OR 1=1");
4241
expect(finishedQuery).toEqual("SELECT * FROM table WHERE id = 123 AND size = 'middle'' OR 1=1'");
4342
});
4443

4544
it("prevents (middle' OR 'a'=a') SQL injection by escaping single quotations ", () => {
4645
const finishedQuery = builder.build(query, 123, "middle' OR 'a'='a",);
4746
expect(finishedQuery).toEqual("SELECT * FROM table WHERE id = 123 AND size = 'middle'' OR ''a''=''a'");
4847
});
48+
49+
it("throws an error if user tries to add a semicolon into a string param not wrapped in quotes", () => {
50+
expect(() => { builder.build(query, ['123; DROP tables WHERE size = '], 'middle') }).toThrow(InappropriateStringParamError);
51+
});
4952
});
5053
});

ksqljs/customErrors.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/** This file contains custom Node.js errors that extends the built-in Error class
2+
* REF: https://rclayton.silvrback.com/custom-errors-in-node-js
3+
* REF: https://futurestud.io/tutorials/node-js-create-your-custom-error
4+
*/
5+
6+
// used to wrap error received from ksqlDB server
7+
class ksqlDBError extends Error {
8+
constructor(error) {
9+
super(error.message)
10+
11+
// Ensure the name of this error is the same as the class name
12+
this.name = this.constructor.name
13+
14+
// capturing the stack trace keeps the reference to your error class
15+
Error.captureStackTrace(this, this.constructor);
16+
17+
// you may also assign additional properties to your error
18+
//this.status = 404
19+
}
20+
}
21+
22+
// for returning error related to use of queryBuilder class
23+
class QueryBuilderError extends Error {
24+
constructor(message) {
25+
super(message)
26+
this.name = this.constructor.name
27+
Error.captureStackTrace(this, this.constructor);
28+
}
29+
}
30+
31+
class EmptyQueryError extends QueryBuilderError {
32+
constructor() {
33+
super('Query should not be empty, undefined, or null');
34+
}
35+
}
36+
37+
class NumParamsError extends QueryBuilderError {
38+
constructor(message) {
39+
super(message);
40+
}
41+
}
42+
43+
class InappropriateStringParamError extends QueryBuilderError {
44+
constructor(message) {
45+
super(message);
46+
}
47+
}
48+
49+
module.exports = {
50+
ksqlDBError,
51+
QueryBuilderError,
52+
EmptyQueryError,
53+
NumParamsError,
54+
InappropriateStringParamError
55+
};

0 commit comments

Comments
 (0)