@oino-ts/db 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,15 +6,19 @@
6
6
 
7
7
  import { expect, test } from "bun:test";
8
8
 
9
- import { OINODbApi, OINODbApiParams, OINOContentType, OINODataRow, OINODbDataField, OINOStringDataField, OINODb, OINODbFactory, OINODbParams, OINODbMemoryDataSet, OINODbModelSet, OINOBenchmark, OINOConsoleLog, OINORequestParams, OINODbSqlFilter, OINODbConfig, OINODbSqlOrder, OINOLogLevel, OINOLog } from "./index.js";
9
+ import { OINODbApi, OINODbApiParams, OINOContentType, OINODataRow, OINODbDataField, OINOStringDataField, OINODb, OINODbFactory, OINODbParams, OINODbMemoryDataSet, OINODbModelSet, OINOBenchmark, OINOConsoleLog, OINORequestParams, OINODbSqlFilter, OINODbConfig, OINODbSqlOrder, OINOLogLevel, OINOLog, OINODbSqlLimit, OINODbApiResult, OINODbSqlComparison, OINONumberDataField, OINODatetimeDataField } from "./index.js";
10
10
 
11
11
  import { OINODbBunSqlite } from "@oino-ts/db-bunsqlite"
12
12
  import { OINODbPostgresql } from "@oino-ts/db-postgresql"
13
13
  import { OINODbMariadb } from "@oino-ts/db-mariadb"
14
14
  import { OINODbMsSql } from "@oino-ts/db-mssql"
15
15
 
16
+ const OINODB_POSTGRESQL_TOKEN = process.env.OINODB_POSTGRESQL_TOKEN || console.error("OINODB_POSTGRESQL_TOKEN not set")
17
+ const OINODB_MARIADB_TOKEN = process.env.OINODB_MARIADB_TOKEN || console.error("OINODB_MARIADB_TOKEN not set")
18
+ const OINOCLOUD_POC_DB_TOKEN = process.env.OINOCLOUD_POC_DB_TOKEN || console.error("OINOCLOUD_POC_DB_TOKEN not set")
16
19
 
17
- type OINOTestApiParams = {
20
+ type OINOTestParams = {
21
+ name: string
18
22
  apiParams: OINODbApiParams
19
23
  requestParams: OINORequestParams
20
24
  postRow: OINODataRow
@@ -23,45 +27,78 @@ type OINOTestApiParams = {
23
27
 
24
28
  const dbs:OINODbParams[] = [
25
29
  { type: "OINODbBunSqlite", url:"file://../localDb/northwind.sqlite", database: "Northwind" },
26
- { type: "OINODbPostgresql", url: "localhost", database: "Northwind", port:5432, user: "node", password: Bun.env.OINODB_POSTGRESQL_TOKEN },
27
- { type: "OINODbMariadb", url: "127.0.0.1", database: "Northwind", port:6543, user: "node", password: Bun.env.OINODB_MARIADB_TOKEN },
28
- { type: "OINODbMsSql", url: "oinocloud-poc-db-srv.database.windows.net", database: "Northwind", port:1433, user: "oinocloud-poc-db-srv-admin", password: Bun.env.OINOCLOUD_POC_DB_SRV }
30
+ { type: "OINODbPostgresql", url: "localhost", database: "Northwind", port:5432, user: "node", password: OINODB_POSTGRESQL_TOKEN },
31
+ { type: "OINODbMariadb", url: "127.0.0.1", database: "Northwind", port:6543, user: "node", password: OINODB_MARIADB_TOKEN },
32
+ { type: "OINODbMsSql", url: "oinocloud-poc-db-srv.database.windows.net", database: "Northwind", port:1433, user: "oinocloud-poc-db-srv-admin", password: OINOCLOUD_POC_DB_TOKEN }
29
33
  ]
30
34
 
31
- const api_tests:OINOTestApiParams[] = [
35
+ const api_tests:OINOTestParams[] = [
32
36
  {
37
+ name: "API",
33
38
  apiParams: { tableName: "Orders" },
34
39
  requestParams: {
35
- sqlParams: { filter: OINODbSqlFilter.parse("(ShipPostalCode)-like(0502%)"), order: OINODbSqlOrder.parse("ShipPostalCode desc") }
40
+ sqlParams: { filter: OINODbSqlFilter.parse("(ShipPostalCode)-like(0502%)"), order: OINODbSqlOrder.parse("ShipPostalCode desc,Freight asc"), limit: OINODbSqlLimit.parse("5 page 2") }
36
41
  },
37
42
  postRow: [30000,"CACTU",1,new Date("2024-04-05"),new Date("2024-04-06"),new Date("2024-04-07"),2,"184.75","a'b\"c%d_e\tf\rg\nh\\i","Garden House Crowther Way","Cowes","British Isles","PO31 7PJ","UK"],
38
43
  putRow: [30000,"CACTU",1,new Date("2023-04-05"),new Date("2023-04-06"),new Date("2023-04-07"),2,"847.51","k'l\"m%n_o\tp\rq\nr\\s","59 rue de l'Abbaye","Cowes2","Western Europe","PO31 8PJ","UK"]
39
44
  },
40
45
  {
46
+ name: "API",
41
47
  apiParams: { tableName: "Products", failOnOversizedValues: true },
42
48
  requestParams: {
43
- sqlParams: { filter: OINODbSqlFilter.parse("(UnitsInStock)-le(5)"), order: OINODbSqlOrder.parse("UnitsInStock asc,UnitPrice asc") }
49
+ sqlParams: { filter: OINODbSqlFilter.parse("(UnitsInStock)-le(5)"), order: OINODbSqlOrder.parse("UnitsInStock asc,UnitPrice asc"), limit: OINODbSqlLimit.parse("7") }
44
50
  },
45
51
  postRow: [99, "Umeshu", 1, 1, "500 ml", 12.99, 2, 0, 20, 0],
46
52
  putRow: [99, "Umeshu", 1, 1, undefined, 24.99, 3, 0, 20, 0]
47
53
  },
48
54
  {
55
+ name: "API",
49
56
  apiParams: { tableName: "Employees", hashidKey: "12345678901234567890123456789012", hashidStaticIds:true },
50
57
  requestParams: {
51
- sqlParams: { filter: OINODbSqlFilter.parse("(TitleOfCourtesy)-eq(Ms.)"), order: OINODbSqlOrder.parse("LastName asc") }
58
+ sqlParams: { filter: OINODbSqlFilter.parse("(TitleOfCourtesy)-eq(Ms.)"), order: OINODbSqlOrder.parse("LastName asc"), limit: OINODbSqlLimit.parse("5") }
52
59
  },
53
60
  postRow: [99, "LastName", "FirstName", "Title", "TitleOfCourtesy", new Date("2024-04-06"), new Date("2024-04-07"), "Address", "City", "Region", 12345, "EU", "123 456 7890", "9876", Buffer.from("0001020304", "hex"), "Line1\nLine2", 1, "http://accweb/emmployees/lastnamefirstname.bmp"],
54
61
  putRow: [99, "LastName2", "FirstName2", null, "TitleOfCourtesy2", new Date("2023-04-06"), new Date("2023-04-07"), "Address2", "City2", "Region2", 54321, "EU2", "234 567 8901", "8765", Buffer.from("0506070809", "hex"), "Line3\nLine4", 1, "http://accweb/emmployees/lastnamefirstname.bmp"],
55
62
  },
56
63
  {
64
+ name: "API",
57
65
  apiParams: { tableName: "OrderDetails" },
58
66
  requestParams: {
59
- sqlParams: { filter: OINODbSqlFilter.parse("(Quantity)-gt(100)"), order: OINODbSqlOrder.parse("Quantity desc") }
67
+ sqlParams: { filter: OINODbSqlFilter.parse("(Quantity)-gt(100)"), order: OINODbSqlOrder.parse("Quantity desc,UnitPrice asc"), limit: OINODbSqlLimit.parse("5 page 2") }
60
68
  },
61
69
  postRow: [10249,77,12.34,56,0],
62
70
  putRow: [10249,77,23.45,67,0]
63
71
  }
72
+ ]
64
73
 
74
+ const owasp_tests:OINOTestParams[] = [
75
+ {
76
+ name: "OWASP 1",
77
+ apiParams: { tableName: "Products", failOnOversizedValues: true },
78
+ requestParams: {
79
+ sqlParams: { filter: OINODbSqlFilter.parse("(1)-eq(1)") }
80
+ },
81
+ postRow: [99, "' FOO", 1, 1],
82
+ putRow: [99, "; FOO", 1, 1]
83
+ },
84
+ {
85
+ name: "OWASP 2",
86
+ apiParams: { tableName: "Products", failOnOversizedValues: true },
87
+ requestParams: {
88
+ sqlParams: { order: OINODbSqlOrder.parse("1 asc") }
89
+ },
90
+ postRow: [99, "' FOO", 1, 1],
91
+ putRow: [99, "; FOO", 1, 1]
92
+ },
93
+ {
94
+ name: "OWASP 3",
95
+ apiParams: { tableName: "Products", failOnOversizedValues: true },
96
+ requestParams: {
97
+ sqlParams: { filter: OINODbSqlFilter.parse("(ProductID)-eq(FOO)") }
98
+ },
99
+ postRow: [99, "\" FOO", 1, 1],
100
+ putRow: [99, "\\ FOO", 1, 1]
101
+ }
65
102
  ]
66
103
 
67
104
  Math.random()
@@ -89,50 +126,54 @@ function encodeResult(o:any|undefined):string {
89
126
  })
90
127
  }
91
128
 
92
- export async function OINOTestApi(dbParams:OINODbParams, apiDataset: OINOTestApiParams) {
129
+ export async function OINOTestApi(dbParams:OINODbParams, testParams: OINOTestParams) {
93
130
  // OINOLog.info("OINOTestApi", {dbParams:dbParams, apiDataset:apiDataset})
94
131
  const db:OINODb = await OINODbFactory.createDb( dbParams )
95
- const api:OINODbApi = await OINODbFactory.createApi(db, apiDataset.apiParams)
132
+ const api:OINODbApi = await OINODbFactory.createApi(db, testParams.apiParams)
96
133
 
97
- const post_dataset:OINODbMemoryDataSet = new OINODbMemoryDataSet([apiDataset.postRow])
134
+ const post_dataset:OINODbMemoryDataSet = new OINODbMemoryDataSet([testParams.postRow])
98
135
  const post_modelset:OINODbModelSet = new OINODbModelSet(api.datamodel, post_dataset)
99
136
 
100
- const put_dataset:OINODbMemoryDataSet = new OINODbMemoryDataSet([apiDataset.putRow])
137
+ const put_dataset:OINODbMemoryDataSet = new OINODbMemoryDataSet([testParams.putRow])
101
138
  const put_modelset:OINODbModelSet = new OINODbModelSet(api.datamodel, put_dataset)
102
139
 
103
140
  // const new_row_id:string = OINODbConfig.printOINOId(post_modelset.datamodel.getRowPrimarykeyValues(apiDataset.postRow))
104
- const new_row_id:string = OINODbConfig.printOINOId(post_modelset.datamodel.getRowPrimarykeyValues(apiDataset.postRow, true))
141
+ const new_row_id:string = OINODbConfig.printOINOId(post_modelset.datamodel.getRowPrimarykeyValues(testParams.postRow, true))
105
142
  // OINOLog.debug("OINOTestApi", {new_row_id:new_row_id})
106
143
 
107
144
  const empty_params:OINORequestParams = { sqlParams: {}}
108
- const request_params:OINORequestParams = Object.assign({}, apiDataset.requestParams)
145
+ const request_params:OINORequestParams = Object.assign({}, testParams.requestParams)
109
146
  request_params.sqlParams = {}
110
147
  const request_params_with_filters:OINORequestParams = Object.assign({}, request_params)
111
- request_params_with_filters.sqlParams = apiDataset.requestParams.sqlParams
148
+ request_params_with_filters.sqlParams = testParams.requestParams.sqlParams
112
149
  // OINOLog.debug("OINOTestApi", {request_params:request_params, request_params_with_filters:request_params_with_filters})
113
150
 
151
+ let target_name:string = ""
152
+ if (testParams.name) {
153
+ target_name = "[" + testParams.name + "]"
154
+ }
114
155
  const target_db:string = "[" + dbParams.type + "]"
115
- let target_table:string = "[" + apiDataset.apiParams.tableName + "]"
156
+ let target_table:string = "[" + testParams.apiParams.tableName + "]"
116
157
  let target_group:string = "[SCHEMA]"
117
158
 
118
159
  // test("dummy", () => {
119
160
  // expect({foo:"h\\i"}).toMatchSnapshot()
120
161
  // })
121
162
 
122
- test(target_db + target_table + target_group + " public properties", async () => {
123
- expect(api.datamodel.printFieldPublicPropertiesJson()).toMatchSnapshot()
163
+ test(target_name + target_db + target_table + target_group + " public properties", async () => {
164
+ expect(api.datamodel.printFieldPublicPropertiesJson()).toMatchSnapshot("SCHEMA")
124
165
  })
125
166
 
126
167
  target_group = "[HTTP GET]"
127
- test(target_db + target_table + target_group + " select *", async () => {
168
+ test(target_name + target_db + target_table + target_group + " select *", async () => {
128
169
  expect(encodeData(await (await api.doRequest("GET", "", "", empty_params)).data?.writeString())).toMatchSnapshot("GET JSON")
129
170
  })
130
171
 
131
- test(target_db + target_table + target_group + " select *", async () => {
172
+ test(target_name + target_db + target_table + target_group + " select *", async () => {
132
173
  expect(encodeData(await (await api.doRequest("GET", "", "", empty_params)).data?.writeString(OINOContentType.csv))).toMatchSnapshot("GET CSV")
133
174
  })
134
175
 
135
- test(target_db + target_table + target_group + " select * with filter", async () => {
176
+ test(target_name + target_db + target_table + target_group + " select * with filter", async () => {
136
177
  expect(encodeData(await (await api.doRequest("GET", "", "", request_params_with_filters)).data?.writeString())).toMatchSnapshot("GET JSON FILTER")
137
178
  })
138
179
 
@@ -143,25 +184,25 @@ export async function OINOTestApi(dbParams:OINODbParams, apiDataset: OINOTestApi
143
184
  target_group = "[HTTP POST]"
144
185
  const post_body_json:string = await post_modelset.writeString(OINOContentType.json)
145
186
  // OINOLog.info("HTTP POST json", {post_body_json:post_body_json})
146
- test(target_db + target_table + target_group + " insert with id", async () => {
187
+ test(target_name + target_db + target_table + target_group + " insert with id", async () => {
147
188
  expect(encodeResult((await api.doRequest("POST", new_row_id, post_body_json, empty_params)))).toMatchSnapshot("POST")
148
189
  })
149
- test(target_db + target_table + target_group + " insert", async () => {
190
+ test(target_name + target_db + target_table + target_group + " insert", async () => {
150
191
  expect(encodeResult((await api.doRequest("POST", "", post_body_json, empty_params)))).toMatchSnapshot("POST")
151
192
  expect(encodeData(await (await api.doRequest("GET", new_row_id, "", empty_params)).data?.writeString())).toMatchSnapshot("GET JSON")
152
193
  expect(encodeData(await (await api.doRequest("GET", new_row_id, "", empty_params)).data?.writeString(OINOContentType.csv))).toMatchSnapshot("GET CSV")
153
194
  })
154
- test(target_db + target_table + target_group + " insert no data", async () => {
155
- expect(encodeResult((await api.doRequest("POST", "", "", empty_params)))).toMatchSnapshot("POST")
195
+ test(target_name + target_db + target_table + target_group + " insert no data", async () => {
196
+ expect(encodeResult((await api.doRequest("POST", "", "{}", empty_params)))).toMatchSnapshot("POST")
156
197
  })
157
- test(target_db + target_table + target_group + " insert duplicate", async () => {
198
+ test(target_name + target_db + target_table + target_group + " insert duplicate", async () => {
158
199
  expect(encodeResult((await api.doRequest("POST", "", post_body_json, empty_params)))).toMatchSnapshot("POST")
159
200
  })
160
201
 
161
202
  target_group = "[HTTP PUT]"
162
203
  const put_body_json = await put_modelset.writeString(OINOContentType.json)
163
204
  // OINOLog.info("HTTP PUT JSON", {put_body_json:put_body_json})
164
- test(target_db + target_table + target_group + " update JSON", async () => {
205
+ test(target_name + target_db + target_table + target_group + " update JSON", async () => {
165
206
  request_params.requestType = OINOContentType.json
166
207
  expect(encodeResult((await api.doRequest("PUT", new_row_id, post_body_json, empty_params)))).toMatchSnapshot("PUT JSON reset")
167
208
  expect(encodeResult((await api.doRequest("PUT", new_row_id, put_body_json, request_params)))).toMatchSnapshot("PUT JSON")
@@ -171,7 +212,7 @@ export async function OINOTestApi(dbParams:OINODbParams, apiDataset: OINOTestApi
171
212
  put_dataset.first()
172
213
  const put_body_csv = await put_modelset.writeString(OINOContentType.csv)
173
214
  // OINOLog.info("HTTP PUT csv", {put_body_csv:put_body_csv})
174
- test(target_db + target_table + target_group + " update CSV", async () => {
215
+ test(target_name + target_db + target_table + target_group + " update CSV", async () => {
175
216
  request_params.requestType = OINOContentType.csv
176
217
  expect(encodeResult((await api.doRequest("PUT", new_row_id, post_body_json, empty_params)))).toMatchSnapshot("PUT CSV reset")
177
218
  expect(encodeResult((await api.doRequest("PUT", new_row_id, put_body_csv, request_params)))).toMatchSnapshot("PUT CSV")
@@ -183,7 +224,7 @@ export async function OINOTestApi(dbParams:OINODbParams, apiDataset: OINOTestApi
183
224
  const multipart_boundary = put_body_formdata.substring(0, put_body_formdata.indexOf('\r'))
184
225
  put_body_formdata = put_body_formdata.replaceAll(multipart_boundary, "---------OINO999999999")
185
226
  // OINOLog.info("HTTP PUT FORMDATA", {put_body_formdata:put_body_formdata})
186
- test(target_db + target_table + target_group + " update FORMDATA", async () => {
227
+ test(target_name + target_db + target_table + target_group + " update FORMDATA", async () => {
187
228
  request_params.requestType = OINOContentType.formdata
188
229
  request_params.multipartBoundary = "---------OINO999999999"
189
230
  expect(encodeResult(await (await api.doRequest("PUT", new_row_id, post_body_json, empty_params)))).toMatchSnapshot("PUT FORMDATA reset")
@@ -195,7 +236,7 @@ export async function OINOTestApi(dbParams:OINODbParams, apiDataset: OINOTestApi
195
236
  put_dataset.first()
196
237
  const put_body_urlencode = await put_modelset.writeString(OINOContentType.urlencode)
197
238
  // OINOLog.info("HTTP PUT URLENCODE", {put_body_urlencode:put_body_urlencode})
198
- test(target_db + target_table + target_group + " update URLENCODE", async () => {
239
+ test(target_name + target_db + target_table + target_group + " update URLENCODE", async () => {
199
240
  request_params.requestType = OINOContentType.urlencode
200
241
  request_params.multipartBoundary = undefined // for some reason this needs reset here so previous test value settings does not leak
201
242
  expect(encodeResult((await api.doRequest("PUT", new_row_id, post_body_json, empty_params)))).toMatchSnapshot("PUT URLENCODE reset")
@@ -203,49 +244,133 @@ export async function OINOTestApi(dbParams:OINODbParams, apiDataset: OINOTestApi
203
244
  expect(encodeData(await (await api.doRequest("GET", new_row_id, "", empty_params)).data?.writeString(OINOContentType.urlencode))).toMatchSnapshot("GET URLENCODE")
204
245
  })
205
246
 
206
- test(target_db + target_table + target_group + " update no data", async () => {
207
- expect(encodeResult((await api.doRequest("PUT", new_row_id, "", empty_params)))).toMatchSnapshot("PUT")
247
+ test(target_name + target_db + target_table + target_group + " update no data", async () => {
248
+ expect(encodeResult((await api.doRequest("PUT", new_row_id, "{}", empty_params)))).toMatchSnapshot("PUT")
208
249
  })
209
250
 
210
251
  const primary_keys:OINODbDataField[] = api.datamodel.filterFields((field:OINODbDataField) => { return field.fieldParams.isPrimaryKey })
211
252
  if (primary_keys.length != 1) {
212
- OINOLog.info("HTTP PUT table " + apiDataset.apiParams.tableName + " does not have an individual primary key so 'invalid null' and 'oversized data' tests are skipped")
253
+ OINOLog.info("HTTP PUT table " + testParams.apiParams.tableName + " does not have an individual primary key so 'invalid null' and 'oversized data' tests are skipped")
213
254
  } else {
214
255
  const id_field:string = primary_keys[0].name
215
256
  const notnull_fields:OINODbDataField[] = api.datamodel.filterFields((field:OINODbDataField) => { return (field.fieldParams.isPrimaryKey == false) && (field.fieldParams.isNotNull == true) })
216
257
  if (notnull_fields.length > 0) {
217
258
  const invalid_null_value = "[{\"" + id_field + "\":\"" + new_row_id + "\",\"" + notnull_fields[0].name + "\":null}]"
218
- test(target_db + target_table + target_group + " update with invalid null value", async () => {
219
- expect(encodeResult((await api.doRequest("PUT", new_row_id, invalid_null_value, empty_params)))).toMatchSnapshot("PUT")
259
+ test(target_name + target_db + target_table + target_group + " update with invalid null value", async () => {
260
+ expect(encodeResult((await api.doRequest("PUT", new_row_id, invalid_null_value, empty_params)))).toMatchSnapshot("PUT invalid null")
220
261
  })
221
262
  }
222
263
  const maxsize_fields:OINODbDataField[] = api.datamodel.filterFields((field:OINODbDataField) => { return (field instanceof OINOStringDataField) && (field.fieldParams.isPrimaryKey == false) && (field.maxLength > 0) })
223
264
  if (maxsize_fields.length > 0) {
224
265
  const oversized_value = "[{\"" + id_field + "\":\"" + new_row_id + "\",\"" + maxsize_fields[0].name + "\":\"" + "".padEnd(maxsize_fields[0].maxLength+1, "z") + "\"}]"
225
- test(target_db + target_table + target_group + " update with oversized data", async () => {
226
- expect(encodeResult((await api.doRequest("PUT", new_row_id, oversized_value, empty_params)))).toMatchSnapshot("PUT")
266
+ test(target_name + target_db + target_table + target_group + " update with oversized data", async () => {
267
+ expect(encodeResult((await api.doRequest("PUT", new_row_id, oversized_value, empty_params)))).toMatchSnapshot("PUT oversized value")
268
+ })
269
+ }
270
+ const numeric_fields:OINODbDataField[] = api.datamodel.filterFields((field:OINODbDataField) => { return (field instanceof OINONumberDataField) && (field.fieldParams.isPrimaryKey == false) })
271
+ if (numeric_fields.length > 0) {
272
+ const nan_value = "[{\"" + id_field + "\":\"" + new_row_id + "\",\"" + numeric_fields[0].name + "\":\"" + "; FOO" + "\"}]"
273
+ OINOLog.debug("HTTP PUT NAN-value", {nan_value:nan_value})
274
+ test(target_name + target_db + target_table + target_group + " update NAN-value", async () => {
275
+ expect(encodeResult((await api.doRequest("PUT", new_row_id, nan_value, empty_params)))).toMatchSnapshot("PUT NAN-value")
276
+ })
277
+ }
278
+ const date_fields:OINODbDataField[] = api.datamodel.filterFields((field:OINODbDataField) => { return (field instanceof OINODatetimeDataField) && (field.fieldParams.isPrimaryKey == false) })
279
+ if (date_fields.length > 0) {
280
+ const non_date = "[{\"" + id_field + "\":\"" + new_row_id + "\",\"" + date_fields[0].name + "\":\"" + "; FOO" + "\"}]"
281
+ OINOLog.debug("HTTP PUT invalid date value", {non_date:non_date})
282
+ test(target_name + target_db + target_table + target_group + " update invalid date value", async () => {
283
+ expect(encodeResult((await api.doRequest("PUT", new_row_id, non_date, empty_params)))).toMatchSnapshot("PUT invalid date value")
227
284
  })
228
285
  }
229
286
  }
230
287
 
231
288
  target_group = "[HTTP DELETE]"
232
- test(target_db + target_table + target_group + " remove", async () => {
289
+ test(target_name + target_db + target_table + target_group + " remove", async () => {
233
290
  expect(encodeResult((await api.doRequest("DELETE", new_row_id, "", empty_params)))).toMatchSnapshot("DELETE")
234
291
  expect(encodeData(await (await api.doRequest("GET", new_row_id, "", empty_params)).data?.writeString())).toMatchSnapshot("GET JSON")
235
292
  })
236
293
  }
237
294
 
295
+ export async function OINOTestOwasp(dbParams:OINODbParams, testParams: OINOTestParams) {
296
+ // OINOLog.info("OINOTestOwasp", {dbParams:dbParams, apiDataset:testParams})
297
+ const db:OINODb = await OINODbFactory.createDb( dbParams )
298
+ const api:OINODbApi = await OINODbFactory.createApi(db, testParams.apiParams)
299
+
300
+ const post_dataset:OINODbMemoryDataSet = new OINODbMemoryDataSet([testParams.postRow])
301
+ const post_modelset:OINODbModelSet = new OINODbModelSet(api.datamodel, post_dataset)
302
+
303
+ const put_dataset:OINODbMemoryDataSet = new OINODbMemoryDataSet([testParams.putRow])
304
+ const put_modelset:OINODbModelSet = new OINODbModelSet(api.datamodel, put_dataset)
305
+
306
+ const new_row_id:string = OINODbConfig.printOINOId(post_modelset.datamodel.getRowPrimarykeyValues(testParams.postRow, true))
307
+ // OINOLog.debug("OINOTestOwasp", {new_row_id:new_row_id})
308
+
309
+ const empty_params:OINORequestParams = { sqlParams: {}}
310
+ const request_params:OINORequestParams = Object.assign({}, testParams.requestParams)
311
+ request_params.sqlParams = {}
312
+ const request_params_with_filters:OINORequestParams = Object.assign({}, request_params)
313
+ request_params_with_filters.sqlParams = testParams.requestParams.sqlParams
314
+ // OINOLog.debug("OINOTestOwasp", {request_params:request_params, request_params_with_filters:request_params_with_filters})
315
+
316
+ let target_name:string = ""
317
+ if (testParams.name) {
318
+ target_name = "[" + testParams.name + "]"
319
+ }
320
+ const target_db:string = "[" + dbParams.type + "]"
321
+ let target_table:string = "[" + testParams.apiParams.tableName + "]"
322
+
323
+ let target_group = "[OWASP GET]"
324
+ test(target_name + target_db + target_table + target_group + " GET with filter", async () => {
325
+ const get_res:OINODbApiResult = await api.doRequest("GET", "", "", request_params_with_filters)
326
+ if (get_res.success) {
327
+ expect(encodeData(await get_res.data?.writeString())).toMatchSnapshot("OWASP GET DATA")
328
+ } else {
329
+ expect(encodeResult(get_res)).toMatchSnapshot("OWASP GET RESULT")
330
+ }
331
+ })
332
+ target_group = "[OWASP POST]"
333
+ test(target_name + target_db + target_table + target_group + " POST", async () => {
334
+ const post_body_json:string = await post_modelset.writeString(OINOContentType.json)
335
+ const post_res:OINODbApiResult = await api.doRequest("POST", "", post_body_json, request_params)
336
+ expect(encodeResult(post_res)).toMatchSnapshot("OWASP POST RESULT")
337
+ expect(encodeData(await (await api.doRequest("GET", new_row_id, "", empty_params)).data?.writeString())).toMatchSnapshot("POST JSON")
338
+ })
339
+
340
+ target_group = "[OWASP PUT]"
341
+ test(target_name + target_db + target_table + target_group + " PUT", async () => {
342
+ const put_body_json:string = await put_modelset.writeString(OINOContentType.json)
343
+ const post_res:OINODbApiResult = await api.doRequest("PUT", new_row_id, put_body_json, request_params)
344
+ expect(encodeResult(post_res)).toMatchSnapshot("OWASP PUT RESULT")
345
+ expect(encodeData(await (await api.doRequest("GET", new_row_id, "", empty_params)).data?.writeString())).toMatchSnapshot("PUT JSON")
346
+ })
347
+
348
+ target_group = "[OWASP DELETE]"
349
+ test(target_name + target_db + target_table + target_group + " DELETE", async () => {
350
+ expect(encodeResult((await api.doRequest("DELETE", new_row_id, "", empty_params)))).toMatchSnapshot("DELETE")
351
+ expect(encodeData(await (await api.doRequest("GET", new_row_id, "", empty_params)).data?.writeString())).toMatchSnapshot("DELETE JSON")
352
+ })
353
+ }
354
+
355
+
238
356
  for (let db of dbs) {
239
357
  for (let api_test of api_tests) {
240
358
  await OINOTestApi(db, api_test)
241
359
  }
242
360
  }
243
361
 
362
+ for (let db of dbs) {
363
+ for (let owasp_test of owasp_tests) {
364
+ await OINOTestOwasp(db, owasp_test)
365
+ }
366
+ }
367
+
368
+
244
369
  const snapshot_file = Bun.file("./node_modules/@oino-ts/db/src/__snapshots__/OINODbApi.test.ts.snap")
245
370
  await Bun.write("./node_modules/@oino-ts/db/src/__snapshots__/OINODbApi.test.ts.snap.js", snapshot_file) // copy snapshots as .js so require works (note! if run with --update-snapshots, it's still the old file)
246
371
  const snapshots = require("./__snapshots__/OINODbApi.test.ts.snap.js")
247
372
 
248
- const crosscheck_tests:string[] = [
373
+ const api_crosschecks:string[] = [
249
374
  "[HTTP GET] select *: GET JSON 1",
250
375
  "[HTTP POST] insert: GET JSON 1",
251
376
  "[HTTP POST] insert: GET CSV 1",
@@ -255,14 +380,27 @@ const crosscheck_tests:string[] = [
255
380
  "[HTTP PUT] update URLENCODE: GET URLENCODE 1"
256
381
  ]
257
382
 
383
+ const owasp_crosschecks:string[] = [
384
+ "[OWASP POST] POST: POST JSON 1",
385
+ "[OWASP PUT] PUT: OWASP PUT RESULT 1"
386
+ ]
387
+
258
388
  for (let i=0; i<dbs.length-1; i++) {
259
389
  const db1:string = dbs[i].type
260
390
  const db2:string = dbs[i+1].type
261
- for (let api of api_tests) {
262
- const table_name = api.apiParams.tableName
263
- for (let test_name of crosscheck_tests) {
264
- test("cross check {" + db1 + "} and {" + db2 + "} table {" + table_name + "} snapshots on {" + test_name + "}", () => {
265
- expect(snapshots["[" + db1 + "][" + table_name + "]" + test_name]).toMatch(snapshots["[" + db2 + "][" + table_name + "]" + test_name])
391
+ for (let api_test of api_tests) {
392
+ const table_name = api_test.apiParams.tableName
393
+ for (let crosscheck of api_crosschecks) {
394
+ test("cross check {" + db1 + "} and {" + db2 + "} table {" + table_name + "} snapshots on {" + crosscheck + "}", () => {
395
+ expect(snapshots["[" + api_test.name + "][" + db1 + "][" + table_name + "]" + crosscheck]).toMatch(snapshots["[" + api_test.name + "][" + db2 + "][" + table_name + "]" + crosscheck])
396
+ })
397
+ }
398
+ }
399
+ for (let owasp_test of owasp_tests) {
400
+ const table_name = owasp_test.apiParams.tableName
401
+ for (let crosscheck of owasp_crosschecks) {
402
+ test("cross check {" + db1 + "} and {" + db2 + "} table {" + table_name + "} snapshots on {" + crosscheck + "}", () => {
403
+ expect(snapshots["[" + owasp_test.name + "][" + db1 + "][" + table_name + "]" + crosscheck]).toMatch(snapshots["[" + owasp_test.name + "][" + db2 + "][" + table_name + "]" + crosscheck])
266
404
  })
267
405
  }
268
406
  }
package/src/OINODbApi.ts CHANGED
@@ -4,9 +4,10 @@
4
4
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
  */
6
6
 
7
- import { OINODbApiParams, OINODb, OINODbDataSet, OINODbDataModel, OINODbDataField, OINOStringDataField, OINO_ERROR_PREFIX, OINO_WARNING_PREFIX, OINO_INFO_PREFIX, OINODataRow, OINODataCell, OINODbModelSet, OINOBenchmark, OINODbFactory, OINODbApiRequestParams, OINOLog, OINODbConfig, OINOHttpResult, OINOHtmlTemplate, OINONumberDataField, OINOContentType, OINOStr } from "./index.js"
7
+ import { OINODbApiParams, OINODb, OINODbDataSet, OINODbDataModel, OINODbDataField, OINOStringDataField, OINO_ERROR_PREFIX, OINO_WARNING_PREFIX, OINO_INFO_PREFIX, OINODataRow, OINODataCell, OINODbModelSet, OINOBenchmark, OINODbFactory, OINODbApiRequestParams, OINOLog, OINODbConfig, OINOHttpResult, OINOHtmlTemplate, OINONumberDataField } from "./index.js"
8
8
  import { OINOResult } from "@oino-ts/types";
9
9
  import { OINOHashid } from "@oino-ts/hashid"
10
+ import { OINOParser } from "@oino-ts/types";
10
11
 
11
12
  const API_EMPTY_PARAMS:OINODbApiRequestParams = { sqlParams: {} }
12
13
 
@@ -41,7 +42,7 @@ export class OINODbApiResult extends OINOResult {
41
42
  * @param headers Headers to include in the response
42
43
  *
43
44
  */
44
- async createResponseFromResult(headers:Record<string, string> = {}):Promise<Response> {
45
+ async getResponse(headers:Record<string, string> = {}):Promise<Response> {
45
46
  let response:Response|null = null
46
47
  if (this.success && this.data) {
47
48
  const body = await this.data.writeString(this.params.responseType)
@@ -93,11 +94,13 @@ export class OINODbHtmlTemplate extends OINOHtmlTemplate {
93
94
  for (let i=0; i<datamodel.fields.length; i++) {
94
95
  const f:OINODbDataField = datamodel.fields[i]
95
96
  let value:string|null|undefined = f.serializeCell(row[i])
96
- if (f.fieldParams.isPrimaryKey) {
97
+ if (f.fieldParams.isPrimaryKey || f.fieldParams.isForeignKey) {
97
98
  if (value && (f instanceof OINONumberDataField) && (datamodel.api.hashid)) {
98
99
  value = datamodel.api.hashid.encode(value, f.name + " " + row_id_seed)
99
100
  }
100
- primary_key_values.push(value || "")
101
+ if (f.fieldParams.isPrimaryKey) {
102
+ primary_key_values.push(value || "")
103
+ }
101
104
  }
102
105
  // OINOLog.debug("renderFromDbData replace field value", {field:f.name, value:value })
103
106
  this.setVariableFromValue(f.name, value || "")
@@ -193,9 +196,10 @@ export class OINODbApi {
193
196
  }
194
197
 
195
198
  private async _doGet(result:OINODbApiResult, id:string, params:OINODbApiRequestParams):Promise<void> {
196
- const sql:string = this.datamodel.printSqlSelect(id, params.sqlParams || {})
197
- // OINOLog.debug("OINODbApi.doGet sql", {sql:sql})
199
+ let sql:string = ""
198
200
  try {
201
+ sql = this.datamodel.printSqlSelect(id, params.sqlParams || {})
202
+ // OINOLog.debug("OINODbApi.doGet sql", {sql:sql})
199
203
  const sql_res:OINODbDataSet = await this.db.sqlSelect(sql)
200
204
  // OINOLog.debug("OINODbApi.doGet sql_res", {sql_res:sql_res})
201
205
  if (sql_res.hasErrors()) {
@@ -291,20 +295,21 @@ export class OINODbApi {
291
295
  * @param params HTTP URL parameters as key-value-pairs
292
296
  *
293
297
  */
294
- async doRequest(method:string, id: string, body:string|OINODataRow[]|any, params:OINODbApiRequestParams = API_EMPTY_PARAMS):Promise<OINODbApiResult> {
298
+ async doRequest(method:string, id: string, body:string|OINODataRow[]|Buffer|any, params:OINODbApiRequestParams = API_EMPTY_PARAMS):Promise<OINODbApiResult> {
295
299
  OINOBenchmark.start("OINODbApi", "doRequest")
296
300
  // OINOLog.debug("OINODbApi.doRequest enter", {method:method, id:id, body:body, params:params})
297
301
  let result:OINODbApiResult = new OINODbApiResult(params)
298
302
  let rows:OINODataRow[] = []
299
303
  if ((method == "POST") || (method == "PUT")) {
300
- if (Array.isArray(body)) {
301
- rows = body
302
-
303
- } else if (typeof(body) == "object") {
304
- rows = [OINODbFactory.createRowFromObject(this.datamodel, body)]
305
-
306
- } else if (typeof(body) == "string") {
307
- rows = OINODbFactory.createRows(this.datamodel, body, params)
304
+ try {
305
+ if (Array.isArray(body)) {
306
+ rows = body as OINODataRow[]
307
+ } else {
308
+ rows = OINOParser.createRows(this.datamodel, body, params)
309
+ }
310
+
311
+ } catch (e:any) {
312
+ result.setError(400, "Invalid data: " + e.message, "DoRequest")
308
313
  }
309
314
  // OINOLog.debug("OINODbApi.doRequest - OINODataRow rows", {rows:rows})
310
315
  }
@@ -4,7 +4,7 @@
4
4
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
  */
6
6
 
7
- import { OINODbDataFieldParams, OINODataCell, OINODb } from "./index.js";
7
+ import { OINODbDataFieldParams, OINODataCell, OINODb, OINOLog, OINO_ERROR_PREFIX } from "./index.js";
8
8
 
9
9
  /**
10
10
  * Base class for a column of data responsible for appropriatelly serializing/deserializing the data.
@@ -62,6 +62,9 @@ export class OINODbDataField {
62
62
  if (this.fieldParams.isPrimaryKey) {
63
63
  params += "PK ";
64
64
  }
65
+ if (this.fieldParams.isForeignKey) {
66
+ params += "FK ";
67
+ }
65
68
  if (this.fieldParams.isAutoInc) {
66
69
  params += "AUTOINC ";
67
70
  }
@@ -253,7 +256,12 @@ export class OINONumberDataField extends OINODbDataField {
253
256
  } else if ((value === "") || (value === null)) {
254
257
  return null
255
258
  } else {
256
- return Number.parseFloat(value)
259
+ const result:number = parseFloat(value)
260
+ if (isNaN(result)) {
261
+ OINOLog.error("OINODbSqlFilter.toSql: Invalid value!", {value:value})
262
+ throw new Error(OINO_ERROR_PREFIX + ": OINONumberDataField.deserializeCell - Invalid value '" + value + "'") // incorrectly formatted data could be a security risk, abort processing
263
+ }
264
+ return result
257
265
  }
258
266
  }
259
267
  }
@@ -305,7 +313,7 @@ export class OINOBlobDataField extends OINODbDataField {
305
313
  */
306
314
  deserializeCell(value: string|null|undefined): OINODataCell {
307
315
  if (value == null) {
308
- return new Buffer(0)
316
+ return Buffer.alloc(0)
309
317
 
310
318
  } else {
311
319
  return Buffer.from(value, 'base64') // Blob-field data is base64 encoded and converted internally to UInt8Array / Buffer
@@ -231,25 +231,20 @@ export class OINODbDataModel {
231
231
  *
232
232
  */
233
233
  printSqlSelect(id: string, params:OINODbSqlParams): string {
234
- let result:string = "SELECT " + this._printSqlColumnNames() + " FROM " + this.api.db.printSqlTablename(this.api.params.tableName);
235
- const filter_sql = params.filter?.toSql(this) || ""
234
+ const column_names = this._printSqlColumnNames()
236
235
  const order_sql = params.order?.toSql(this) || ""
237
236
  const limit_sql = params.limit?.toSql(this) || ""
238
- // OINOLog.debug("OINODbDataModel.printSqlSelect", {select_sql:result, filter_sql:filter_sql, order_sql:order_sql})
237
+ const filter_sql = params.filter?.toSql(this) || ""
238
+ let where_sql = ""
239
+ // OINOLog.debug("OINODbDataModel.printSqlSelect", {id:id, select_sql:result, filter_sql:filter_sql, order_sql:order_sql})
239
240
  if ((id != null) && (id != "") && (filter_sql != "")) {
240
- result += "\nWHERE " + this._printSqlPrimaryKeyCondition(id) + " AND " + filter_sql;
241
+ where_sql = this._printSqlPrimaryKeyCondition(id) + " AND " + filter_sql
241
242
  } else if ((id != null) && (id != "")) {
242
- result += "\nWHERE " + this._printSqlPrimaryKeyCondition(id);
243
+ where_sql = this._printSqlPrimaryKeyCondition(id)
243
244
  } else if (filter_sql != "") {
244
- result += "\nWHERE " + filter_sql;
245
- }
246
- if (order_sql) {
247
- result += "\nORDER BY " + order_sql
248
- }
249
- if (limit_sql) {
250
- result += "\nLIMIT " + limit_sql
245
+ where_sql = filter_sql
251
246
  }
252
- result += ";"
247
+ const result = this.api.db.printSqlSelect(this.api.params.tableName, column_names, where_sql, order_sql, limit_sql)
253
248
  // OINOLog.debug("OINODbDataModel.printSqlSelect", {result:result})
254
249
  return result;
255
250
  }