Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 40 additions & 11 deletions lib/axios.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,62 @@
const http = require('http')
const axios = require('axios')

class Axios {
/** @type {axios.Axios | undefined} */
#axios
/** @type {string | undefined} */
url

get axios() {
const axios = require('axios').create ({
const ax = require('axios').default.create ({
headers: { 'Content-Type': 'application/json' },
httpAgent: new http.Agent({ keepAlive: false}),
baseURL: this.url,
})
// fill in baseURL on subsequent this.url = url, after server has started
Reflect.defineProperty (this, 'url', { configurable: true, set: url => {
Reflect.defineProperty (this, 'url', { value: url })
axios.defaults.baseURL = url
axios.default.defaults.baseURL = url
}})
return super.axios = axios
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewrite

to my understanding, this super.… = … technique was used to have a truly private variable. I replaced them with actual private variables.

this.#axios = ax
return this.#axios
}
/** @type {axios.Axios["get"]} **/
get (..._) { return this.axios.get (..._args(_)) .catch(_error) }
/** @type {axios.Axios["put"]} */
put (..._) { return this.axios.put (..._args(_)) .catch(_error) }
/** @type {axios.Axios["post"]} */
post (..._) { return this.axios.post (..._args(_)) .catch(_error) }
/** @type {axios.Axios["patch"]} */
patch (..._) { return this.axios.patch (..._args(_)) .catch(_error) }
/** @type {axios.Axios["delete"]} */
delete (..._) { return this.axios.delete (..._args(_)) .catch(_error) }
/** @type {axios.Axios["options"]} */
options (..._) { return this.axios.options (..._args(_)) .catch(_error) }

/** @type typeof _.get */ get GET() { return this.get .bind (this) }
/** @type typeof _.put */ get PUT() { return this.put .bind (this) }
/** @type typeof _.post */ get POST() { return this.post .bind (this) }
/** @type typeof _.patch */ get PATCH() { return this.patch .bind (this) }
/** @type typeof _.delete */ get DELETE() { return this.delete .bind (this) }
/** @type typeof _.delete */ get DEL() { return this.delete .bind (this) } //> to avoid conflicts with cds.ql.DELETE
/** @type typeof _.options */ get OPTIONS() { return this.options .bind (this) }
/** @type typeof _.get */
get GET() { return this.get .bind (this) }
/** @type typeof _.put */
get PUT() { return this.put .bind (this) }
/** @type typeof _.post */
get POST() { return this.post .bind (this) }
/** @type typeof _.patch */
get PATCH() { return this.patch .bind (this) }
/** @type typeof _.delete */
get DELETE() { return this.delete .bind (this) }
/** @type typeof _.delete */
get DEL() { return this.delete .bind (this) } //> to avoid conflicts with cds.ql.DELETE
/** @type typeof _.options */
get OPTIONS() { return this.options .bind (this) }

}

/**
* @param {any[]} args - args
*/
const _args = (args) => {
const first = args[0], last = args[args.length-1]
const first = args[0]
const last = args[args.length-1]
if (first.raw) {
if (first[first.length-1] === '' && typeof last === 'object')
return [ String.raw(...args.slice(0,-1)), last ]
Expand All @@ -42,6 +67,10 @@ const _args = (args) => {
return args
}

/**
* @typedef {Error & {code:string}} ErrorWithCode
* @param {ErrorWithCode & {errors?: ErrorWithCode[]}} e - errors
*/
const _error = (e) => {
if (e.errors) e = e.errors[0] // Node 20 sends AggregationErrors
if (e.code === 'ERR_INVALID_URL') {// URL wasn't completed due to the non-started server
Expand Down
16 changes: 14 additions & 2 deletions lib/data.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
const cds = require('@sap/cds')

class DataUtil {
/** @type {ReturnType<typeof DELETE.from>[] | undefined} */
_deletes

constructor() {
// This is to support simplified usage like that: beforeEach(test.data.reset)
const {reset} = this; this.reset = (x) => {
const {reset} = this
this.reset = (/** @type{any}*/ x) => {
if (typeof x === 'function') reset.call(this).then(x,x) // x is the done callback of jest -> no return
else if (x?.assert) return reset.call(this) // x is a node --test TestContext object -> ignore
else return reset.call(this,x) // x is a db service instance
Expand All @@ -15,11 +18,17 @@ class DataUtil {
global.beforeEach (() => this.reset())
}

/**
* @param {cds.DatabaseService} db - db
*/
async deploy(db) {
if (!db) db = await cds.connect.to('db')
await cds.deploy.data(db)
}

/**
* @param {cds.DatabaseService} db - db
*/
async delete(db) {
if (!db) db = await cds.connect.to('db')
if (!this._deletes) {
Expand All @@ -38,7 +47,10 @@ class DataUtil {
}
}

/* delete + new deploy from csv */
/**
* delete + new deploy from csv
* @param {cds.DatabaseService} db - db
*/
async reset(db) {
if (!db) db = await cds.connect.to('db')
await this.delete(db)
Expand Down
23 changes: 22 additions & 1 deletion lib/expect.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
const { inspect } = require('node:util')

/**
* @param {{status: any, body: any}} x
*/
const format = x => inspect(
is.error(x) ? x.message
: typeof x === 'object' && 'status' in x && 'body' in x ? { status: x.status, body: x.body }
Expand All @@ -8,22 +12,39 @@ const format = x => inspect(
)

const expect = module.exports = actual => {
const chainable = function (x) { return this.call(x) }; delete chainable.length
Copy link
Author

@daogrady daogrady Mar 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewrite

This caused

The operand of a 'delete' operator cannot be a read-only property.ts(2704)

And it does not seem to have any effect anyway:

Screenshot 2025-03-27 at 14 09 46

const chainable = function (x) {
return this.call(x)
}
return Object.setPrototypeOf(chainable, new Assertion(actual))
}

/**
* @template T
* @typedef {(x: any) => x is T} Is */

const is = new class {
Array = Array.isArray
/** @type {Is<Error>} */
Error = x => x instanceof Error || x?.stack && x.message
/** @type {Is<Symbol>} */
Symbol = x => typeof x === 'symbol'
/** @type {Is<Object>} */
Object = x => typeof x === 'object' // && x && !is.array(x)
/** @type {Is<String>} */
String = x => typeof x === 'string' || x instanceof String
/** @type {Is<Number>} */
Number = x => typeof x === 'number' || x instanceof Number
/** @type {Is<Boolean>} */
Boolean = x => typeof x === 'boolean' || x instanceof Boolean
/** @type {Is<Promise<unknown>>} */
Promise = x => x instanceof Promise
/** @type {Is<RegExp>} */
RegExp = x => x instanceof RegExp
/** @type {Is<Date>} */
Date = x => x instanceof Date
/** @type {Is<Set<unknown>>} */
Set = x => x instanceof Set
/** @type {Is<Map<unknown, unknown>>} */
Map = x => x instanceof Map
array = this.Array
error = this.Error
Expand Down
1 change: 1 addition & 0 deletions lib/reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ module.exports = function report_on (test,o) {

/**
* Adds handlers to debug test stream events.
* @param {string} events - comma-separated list of events to debug
*/
function debug (events) {
inspect.defaultOptions.depth = 11
Expand Down
127 changes: 122 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"node": ">=20"
},
"scripts": {
"check-types": "tsc",
"test": "node --test \"test/**/*.test.js\"",
"test:mocha": "npx -y mocha \"test/**/*.test.js\"",
"test:jest": "npx -y jest \"test/.*\\.test\\.js\"",
Expand All @@ -42,8 +43,9 @@
},
"devDependencies": {
"@cap-js/cds-test": "file://.",
"@cap-js/test-sample-app": "file://./test/app/",
"@cap-js/cds-types": "^0.9.0",
"@cap-js/sqlite": "^1.5.0",
"@cap-js/test-sample-app": "file://./test/app/",
"@sap/cds": "^8.8",
"express": "^4.17.1"
}
Expand Down
25 changes: 25 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"skipLibCheck": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"alwaysStrict": true,
"esModuleInterop": true,
"checkJs": true,
"allowJs": true,
"declaration": true,
"target": "ES2016",
"module": "ESNext",
"moduleResolution": "node",
"outDir": "dist"
},
"include": [
"./lib/**/*.js"
],
"verbose": true
}