diff --git a/.github/workflows/npm-test.yml b/.github/workflows/npm-test.yml index 38584b1..c303d84 100644 --- a/.github/workflows/npm-test.yml +++ b/.github/workflows/npm-test.yml @@ -14,4 +14,9 @@ jobs: - uses: actions/setup-node@v3 with: node-version: 21 - - run: npm test + - uses: oven-sh/setup-bun@v1 + with: + bun-version: 1.1.4 + - run: node --test **/test/**/[!b][!u][!n]*/**/*.js + - run: bun test **/test/**/bun/**/** + diff --git a/README.md b/README.md index 5c29a01..9f4eced 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # objective-http Proxy classes for creating a http server + ## Server -There are all ```Server``` classes feature. -Your endpoints must implement ```Endpoint``` class interface (```route``` and ```handle``` functions). +There are all `Server` classes feature. +Your endpoints must implement `Endpoint` class interface (`route()` and `async handle(request)` methods). -``` javascript +```javascript const http = require('node:http'); const cluster = require('node:cluster'); @@ -28,15 +29,15 @@ const { new ClusteredServer( new LoggedServer( new Server( - http, - new LoggedInputRequest(new JsonInputRequest(new InputRequest()), console), - new LoggedOutputResponse(new JsonOutputResponse(new OutputResponse()), console), new Endpoints([ new MyFirstEndpoint(new LoggedEndpoint(new Endpoint(), console)), new MySecondEndpoint(new LoggedEndpoint(new Endpoint(), console)), new MyThirdEndpoint(new LoggedEndpoint(new Endpoint(), console)) ]), - {port: server_port} + {port: server_port}, + new LoggedInputRequest(new JsonInputRequest(new InputRequest()), console), + new LoggedOutputResponse(new JsonOutputResponse(new OutputResponse()), console), + http ), console ), @@ -45,10 +46,12 @@ new ClusteredServer( ).start(); ``` + ## Client -``` javascript -const https = require('node:https'); +```javascript +const http = require('node:http'); +const requestFunction = http.request; const { OutputRequest, InputResponse @@ -56,8 +59,8 @@ const { const response = await new OutputRequest( - https, new InputResponse(), + requestFunction, { url: 'https://example.com', method: 'POST', @@ -69,7 +72,7 @@ console.log(response.body().toString()); //or -const request = new OutputRequest(https, new InputResponse()); +const request = new OutputRequest(new InputResponse(), requestFunction); const otherResponse = await (request .copy({ @@ -77,6 +80,107 @@ const otherResponse = await (request method: 'POST', body: 'test body' })) - .send() + .send(); + +console.log(response.body().toString()); +``` + + +## [Bun](https://bun.sh) support + +`server` and `client` packages support Bun by default. +But there ara special `bun` package with native [Bun API](https://bun.sh/docs/runtime/bun-apis) implementation (like `Bun.serve()`). +And you should replace `node:http` package with `objective-http.bun.bunttp` in your `Server` configuration. +[Don't use](https://bun.sh/docs/runtime/nodejs-apis#node-cluster) `ClusteredServer` with `Bun`!!! + + +### Server Bun usage + +It should work with `node` and `bun`: + +```javascript +const http = require('node:http'); + +const { + Server, + LoggedServer, + InputRequest, + JsonInputRequest, + LoggedInputRequest, + OutputResponse, + JsonOutputResponse, + LoggedOutputResponse, + Endpoint, + LoggedEndpoint, + Endpoints +} = require('objective-http').server; + +new LoggedServer( + new Server( + new Endpoints([ + new MyFirstEndpoint(new LoggedEndpoint(new Endpoint(), console)), + new MySecondEndpoint(new LoggedEndpoint(new Endpoint(), console)), + new MyThirdEndpoint(new LoggedEndpoint(new Endpoint(), console)) + ]), + {port: server_port}, + new LoggedInputRequest(new JsonInputRequest(new InputRequest()), console), + new LoggedOutputResponse(new JsonOutputResponse(new OutputResponse()), console), + http + ), + console +).start() +``` + +In order for the code to be executed only by `bun` (with `Bun API` inside), you need to make changes to the import block. +`bun` package redeclare only `InputRequest` and `OutputResponse` classes. Other classes taken from `server` package. +```javascript +const http = require('objective-http').bun.bunttp; + +const { + Server, + LoggedServer, + InputRequest, + JsonInputRequest, + LoggedInputRequest, + OutputResponse, + JsonOutputResponse, + LoggedOutputResponse, + Endpoint, + LoggedEndpoint, + Endpoints +} = require('objective-http').bun.server; +``` + + +### Client Bun usage + +It should work with `node` and `bun`: + +```javascript +const http = require('node:http'); +const requestFunction = http.request; +const { + OutputRequest, + InputResponse +} = require('objective-http').client; + +await (new OutputRequest(new InputResponse(), requestFunction) + .copy({ + url: 'https://example.com', + method: 'POST', + body: 'test body' + })) + .send(); +``` + +In order for the code to be executed only by `bun`, you need to make changes to the import block. + +```javascript +const http = require('objective-http').bun.bunttp; +const requestFunction = http.request; +const { + OutputRequest, + InputResponse +} = require('objective-http').bun.client; ``` \ No newline at end of file diff --git a/package.json b/package.json index f4209b5..b3b8c6c 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,17 @@ "version": "master", "private": "true", "description": "Proxy classes for creating a http server", - "keywords": ["web", "web-server", "http", "http-server", "oop"], + "keywords": [ + "web", + "web-server", + "http", + "http-server", + "oop" + ], "scripts": { "prepareToPublish": "node dist/prepareToPublish.js", - "test": "node --test" + "test": "node --test **/test/**/[!b][!u][!n]*/**/*.js", + "testBun": "bun test **/test/**/bun/**/**" }, "author": { "name": "volatilization", @@ -16,4 +23,4 @@ "url": "git+https://github.com/volatilization/objective-http.git" }, "license": "LGPL-3.0-only" -} \ No newline at end of file +} diff --git a/src/js/bun/Bunttp.js b/src/js/bun/Bunttp.js new file mode 100644 index 0000000..2fb05b3 --- /dev/null +++ b/src/js/bun/Bunttp.js @@ -0,0 +1,29 @@ +module.exports = class Bunttp { + #serverConfig; + #server; + + constructor(serverConfig = {}, server = {}) { + this.#serverConfig = serverConfig; + this.#server = server; + } + + createServer(cb) { + this.#serverConfig.fetch = cb; + return this; + } + + listen(options, cb) { + this.#serverConfig.port = options.port; + this.#server = Bun.serve(this.#serverConfig); + cb(); + return this; + } + + close(cb) { + this.#server.stop(); + cb(); + return this; + } + + request = fetch; +} \ No newline at end of file diff --git a/src/js/bun/client/index.js b/src/js/bun/client/index.js new file mode 100644 index 0000000..42d3099 --- /dev/null +++ b/src/js/bun/client/index.js @@ -0,0 +1,4 @@ +module.exports = { + OutputRequest: require('./request/OutputRequest'), + InputResponse: require('./response/InputResponse') +} \ No newline at end of file diff --git a/src/js/bun/client/request/OutputRequest.js b/src/js/bun/client/request/OutputRequest.js new file mode 100644 index 0000000..b5bbcbe --- /dev/null +++ b/src/js/bun/client/request/OutputRequest.js @@ -0,0 +1,26 @@ +module.exports = class OutputRequest { + #response; + #requestFunction; + #options; + + constructor(response, requestFunction, options) { + this.#response = response; + this.#requestFunction = requestFunction; + this.#options = options; + } + + copy(options = this.#options, response = this.#response, requestFunction = this.#requestFunction) { + return new OutputRequest(response, requestFunction, options); + } + + async send() { + try { + return await (this.#response + .copy(await this.#requestFunction(this.#options.url, this.#options))) + .flush() + + } catch (e) { + throw new Error(e.message, {cause: 'INVALID_REQUEST'}); + } + } +} \ No newline at end of file diff --git a/src/js/bun/client/response/InputResponse.js b/src/js/bun/client/response/InputResponse.js new file mode 100644 index 0000000..f87e7ba --- /dev/null +++ b/src/js/bun/client/response/InputResponse.js @@ -0,0 +1,40 @@ +module.exports = class InputResponse { + #inputStream; + #options; + + constructor(inputStream, options) { + this.#inputStream = inputStream; + this.#options = options; + } + + copy(inputStream = this.#inputStream, options = this.#options) { + return new InputResponse(inputStream, options); + } + + async flush() { + try { + return new InputResponse(this.#inputStream, + { + statusCode: this.#inputStream.status, + headers: this.#inputStream.headers, + body: Buffer.from(await (await this.#inputStream.blob()).arrayBuffer()) + } + ); + + } catch (e) { + throw new Error(e.message, {cause: 'INVALID_RESPONSE'}); + } + } + + statusCode() { + return this.#options.statusCode; + } + + headers() { + return this.#options.headers; + } + + body() { + return this.#options.body; + } +} \ No newline at end of file diff --git a/src/js/bun/index.js b/src/js/bun/index.js new file mode 100644 index 0000000..863ddf1 --- /dev/null +++ b/src/js/bun/index.js @@ -0,0 +1,5 @@ +module.exports = { + server: require('./server'), + client: require('./client'), + bunttp: new (require('./Bunttp'))() +} \ No newline at end of file diff --git a/src/js/bun/server/index.js b/src/js/bun/server/index.js new file mode 100644 index 0000000..4031fe6 --- /dev/null +++ b/src/js/bun/server/index.js @@ -0,0 +1,4 @@ +module.exports = { + InputRequest: require('./request/InputRequest'), + OutputResponse: require('./response/OutputResponse') +} \ No newline at end of file diff --git a/src/js/bun/server/request/InputRequest.js b/src/js/bun/server/request/InputRequest.js new file mode 100644 index 0000000..a264b92 --- /dev/null +++ b/src/js/bun/server/request/InputRequest.js @@ -0,0 +1,50 @@ +module.exports = class InputRequest { + #inputStream; + #options; + + constructor(inputStream, options) { + this.#inputStream = inputStream; + this.#options = options; + } + + copy(inputStream = this.#inputStream, options = this.#options) { + return new InputRequest(inputStream, options); + } + + async flush() { + try { + return new InputRequest( + this.#inputStream, + { + method: this.#inputStream.method, + path: new URL(this.#inputStream.url).pathname, + query: new URL(this.#inputStream.url).searchParams, + headers: this.#inputStream.headers, + body: Buffer.from(await (await this.#inputStream.blob()).arrayBuffer()) + } + ); + + } catch (e) { + throw new Error(e.message, {cause: 'INVALID_REQUEST'}); + } + } + + route() { + return { + method: this.#options.method.toString().toUpperCase(), + path: this.#options.path.toString().toLowerCase() + } + } + + query() { + return this.#options.query; + } + + body() { + return this.#options.body; + } + + headers() { + return this.#options.headers; + } +} \ No newline at end of file diff --git a/src/js/bun/server/response/OutputResponse.js b/src/js/bun/server/response/OutputResponse.js new file mode 100644 index 0000000..6f0ebe9 --- /dev/null +++ b/src/js/bun/server/response/OutputResponse.js @@ -0,0 +1,49 @@ +module.exports = class OutputResponse { + #options; + #outputStream; + + constructor(options, outputStream) { + this.#options = {...{statusCode: 200, headers: {}}, ...options}; + this.#outputStream = outputStream; + } + + copy(options = this.#options, outputStream = this.#outputStream) { + return new OutputResponse({...{statusCode: 200, headers: {}}, ...options}, outputStream); + } + + update(options) { + return new OutputResponse(this.#mergeOptions(this.#options, options), this.#outputStream); + } + + flush() { + try { + return new Response(this.#options.body, { + status: this.#options.statusCode, + headers: this.#options.headers + }); + + } catch (e) { + throw new Error(e.message, {cause: 'INVALID_RESPONSE'}) + } + } + + #mergeOptions(existedOptions, newOptions) { + if (newOptions == null) { + return existedOptions; + } + + if (newOptions.statusCode != null) { + existedOptions.statusCode = newOptions.statusCode; + } + + if (newOptions.body != null) { + existedOptions.body = newOptions.body; + } + + if (newOptions.headers != null) { + existedOptions.headers = {...existedOptions.headers, ...newOptions.headers}; + } + + return existedOptions; + } +} \ No newline at end of file diff --git a/src/js/client/index.js b/src/js/client/index.js new file mode 100644 index 0000000..42d3099 --- /dev/null +++ b/src/js/client/index.js @@ -0,0 +1,4 @@ +module.exports = { + OutputRequest: require('./request/OutputRequest'), + InputResponse: require('./response/InputResponse') +} \ No newline at end of file diff --git a/src/js/client/request/OutputRequest.js b/src/js/client/request/OutputRequest.js index e21fe99..c9fa97e 100644 --- a/src/js/client/request/OutputRequest.js +++ b/src/js/client/request/OutputRequest.js @@ -1,23 +1,23 @@ module.exports = class OutputRequest { - #http; #response; + #requestFunction; #options; - constructor(http, response, options) { - this.#http = http; + constructor(response, requestFunction, options) { this.#response = response; + this.#requestFunction = requestFunction; this.#options = {method: 'GET', ...options}; } - copy(options = this.#options, response = this.#response, http = this.#http) { - return new OutputRequest(http, response, {method: 'GET', ...options}); + copy(options = this.#options, response = this.#response, http = this.#requestFunction) { + return new OutputRequest(response, http, {method: 'GET', ...options}); } send() { return new Promise((resolve, reject) => { try { this.#sendRequestOutputStream( - this.#configureRequestOutputStream(this.#http, this.#response, this.#options, resolve, reject), + this.#configureRequestOutputStream(this.#requestFunction, this.#response, this.#options, resolve, reject), this.#options); } catch (e) { @@ -26,9 +26,9 @@ module.exports = class OutputRequest { }); } - #configureRequestOutputStream(http, response, options, resolve, reject) { + #configureRequestOutputStream(requestFunction, response, options, resolve, reject) { if (options.url != null) { - return http.request( + return requestFunction( options.url, options, async (responseInputStream) => { @@ -36,7 +36,7 @@ module.exports = class OutputRequest { }); } - return http.request( + return requestFunction( options, async (responseInputStream) => { await this.#flushResponseInputStream(responseInputStream, response, resolve, reject); diff --git a/src/js/client/response/InputResponse.js b/src/js/client/response/InputResponse.js index 54cf7c7..ac4c68f 100644 --- a/src/js/client/response/InputResponse.js +++ b/src/js/client/response/InputResponse.js @@ -12,21 +12,26 @@ module.exports = class InputResponse { } flush() { - return new Promise((resolve, reject) => { - this.#inputStream.once('error', (e) => - reject(new Error(e.message, {cause: 'INVALID_RESPONSE'})) - ); - - let chunks = []; - this.#inputStream.on('data', (chunk) => chunks.push(chunk)); - this.#inputStream.on('end', () => resolve(new InputResponse(this.#inputStream, - { - statusCode: this.#inputStream.statusCode, - headers: new Headers(this.#inputStream.headers), - body: Buffer.concat(chunks) - } - ))); - }); + try { + return new Promise((resolve, reject) => { + this.#inputStream.once('error', (e) => + reject(new Error(e.message, {cause: 'INVALID_RESPONSE'})) + ); + + let chunks = []; + this.#inputStream.on('data', (chunk) => chunks.push(chunk)); + this.#inputStream.on('end', () => resolve(new InputResponse(this.#inputStream, + { + statusCode: this.#inputStream.statusCode, + headers: new Headers(this.#inputStream.headers), + body: Buffer.concat(chunks) + } + ))); + }); + + } catch (e) { + throw new Error(e.message, {cause: 'INVALID_RESPONSE'}) + } } statusCode() { diff --git a/src/js/index.js b/src/js/index.js index 6ca1b39..ed8bc31 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -1,20 +1,9 @@ module.exports = { - server: { - Server: require('./server/Server'), - LoggedServer: require('./server/LoggedServer'), - ClusteredServer: require('./server/ClusteredServer'), - Endpoint: require('./server/endpoint/Endpoint'), - LoggedEndpoint: require('./server/endpoint/LoggedEndpoint'), - Endpoints: require('./server/endpoint/Endpoints'), - InputRequest: require('./server/request/InputRequest'), - JsonInputRequest: require('./server/request/JsonInputRequest'), - LoggedInputRequest: require('./server/request/LoggedInputRequest'), - OutputResponse: require('./server/response/OutputResponse'), - JsonOutputResponse: require('./server/response/JsonOutputResponse'), - LoggedOutputResponse: require('./server/response/LoggedOutputResponse') - }, - client: { - OutputRequest: require('./client/request/OutputRequest'), - InputResponse: require('./client/response/InputResponse') + server: require('./server'), + client: require('./client'), + bun: { + server: {...require('./server'), ...require('./bun/').server}, + client: {...require('./client'), ...require('./bun').client}, + bunttp: require('./bun').bunttp } } \ No newline at end of file diff --git a/src/js/server/ClusteredServer.js b/src/js/server/ClusteredServer.js index c0ab547..6dcf0f0 100644 --- a/src/js/server/ClusteredServer.js +++ b/src/js/server/ClusteredServer.js @@ -3,10 +3,10 @@ module.exports = class ClusteredServer { #cluster; #options; - constructor(origin, cluster, options) { + constructor(origin, options, cluster) { this.#origin = origin; - this.#cluster = cluster; this.#options = options; + this.#cluster = cluster; } async start() { @@ -20,6 +20,10 @@ module.exports = class ClusteredServer { } } + async stop() { + return new ClusteredServer(await this.#origin.stop(), this.#cluster, this.#options); + } + options() { return {...this.#origin.options(), ...this.#options}; } diff --git a/src/js/server/LoggedServer.js b/src/js/server/LoggedServer.js index a2f5742..581b225 100644 --- a/src/js/server/LoggedServer.js +++ b/src/js/server/LoggedServer.js @@ -10,7 +10,7 @@ module.exports = class LoggedServer { async start() { const server = await this.#origin.start(); - this.#logger.debug(`HttpServer is running at port: ${this.#origin.options().port}`); + this.#logger.debug(`HttpServer is running at port: ${server.options().port}`); return new LoggedServer(server, this.#logger); } @@ -18,7 +18,7 @@ module.exports = class LoggedServer { async stop() { const server = await this.#origin.stop(); - this.#logger.debug(`HttpServer at port: ${this.#origin.options().port} is stopped`); + this.#logger.debug(`HttpServer at port: ${server.options().port} is stopped`); return new LoggedServer(server, this.#logger); } diff --git a/src/js/server/Server.js b/src/js/server/Server.js index 06fbc66..5915875 100644 --- a/src/js/server/Server.js +++ b/src/js/server/Server.js @@ -1,17 +1,17 @@ -module.exports = class HttpServer { - #http; - #request; - #response; +module.exports = class Server { #endpoints; #options; + #request; + #response; + #http; #server; - constructor(http, request, response, endpoints, options, server) { - this.#http = http; - this.#request = request; - this.#response = response; + constructor(endpoints, options, request, response, http, server) { this.#endpoints = endpoints; this.#options = options; + this.#request = request; + this.#response = response; + this.#http = http; this.#server = server; } @@ -19,27 +19,27 @@ module.exports = class HttpServer { const server = this.#http.createServer(async (requestStream, responseStream) => { try { return await (this.#response - .copy(responseStream, await this.#endpoints + .copy(await this.#endpoints .handle(await (this.#request .copy(requestStream)) - .flush()))) + .flush()), responseStream)) .flush(); } catch (e) { if (e.cause === 'INVALID_REQUEST') { - return await (this.#response - .copy(responseStream, { + return this.#response + .copy({ statusCode: 400, body: e.message - })) + }, responseStream) .flush(); } - return await (this.#response - .copy(responseStream, { + return this.#response + .copy({ statusCode: 500, body: 'Unexpected server error.' - })) + }, responseStream) .flush(); } }); @@ -47,13 +47,15 @@ module.exports = class HttpServer { return new Promise(resolve => { server.listen( this.#options, - () => resolve(new HttpServer( - this.#http, - this.#request, - this.#response, - this.#endpoints, - this.#options, - server)) + () => { + resolve(new Server( + this.#endpoints, + this.#options, + this.#request, + this.#response, + this.#http, + server)); + } ); }); } @@ -61,12 +63,12 @@ module.exports = class HttpServer { stop() { return new Promise(resolve => { this.#server.close( - () => resolve(new HttpServer( - this.#http, + () => resolve(new Server( + this.#endpoints, + this.#options, this.#request, this.#response, - this.#endpoints, - this.#options)) + this.#http)) ); }); } diff --git a/src/js/server/index.js b/src/js/server/index.js new file mode 100644 index 0000000..a6618db --- /dev/null +++ b/src/js/server/index.js @@ -0,0 +1,14 @@ +module.exports = { + Server: require('./Server'), + LoggedServer: require('./LoggedServer'), + ClusteredServer: require('./ClusteredServer'), + Endpoint: require('./endpoint/Endpoint'), + LoggedEndpoint: require('./endpoint/LoggedEndpoint'), + Endpoints: require('./endpoint/Endpoints'), + InputRequest: require('./request/InputRequest'), + JsonInputRequest: require('./request/JsonInputRequest'), + LoggedInputRequest: require('./request/LoggedInputRequest'), + OutputResponse: require('./response/OutputResponse'), + JsonOutputResponse: require('./response/JsonOutputResponse'), + LoggedOutputResponse: require('./response/LoggedOutputResponse') +} \ No newline at end of file diff --git a/src/js/server/request/JsonInputRequest.js b/src/js/server/request/JsonInputRequest.js index da13260..97c2ac6 100644 --- a/src/js/server/request/JsonInputRequest.js +++ b/src/js/server/request/JsonInputRequest.js @@ -12,7 +12,7 @@ module.exports = class JsonInputRequest { } async flush() { - if (this.#useChunkMethod(this.#inputStream.method) && !this.#validHeaders(this.#inputStream.headers)) { + if (this.#useChunkMethod(this.#inputStream.method) && !this.#validHeaders(new Headers(this.#inputStream.headers))) { throw new Error('Wrong content-type. Only application/json accepted.', {cause: 'INVALID_REQUEST'}); } @@ -45,7 +45,7 @@ module.exports = class JsonInputRequest { } #validHeaders(requestHeaders) { - return requestHeaders['content-type'] != null - && new RegExp('^application\/json').test(requestHeaders['content-type']); + return new Headers(requestHeaders).has('content-type') + && new RegExp('^application\/json').test(new Headers(requestHeaders).get('content-type')); } } \ No newline at end of file diff --git a/src/js/server/response/JsonOutputResponse.js b/src/js/server/response/JsonOutputResponse.js index ad0669d..b9500c3 100644 --- a/src/js/server/response/JsonOutputResponse.js +++ b/src/js/server/response/JsonOutputResponse.js @@ -1,12 +1,14 @@ module.exports = class JsonOutputResponse { #origin; + #options; - constructor(origin) { + constructor(origin, options) { this.#origin = origin; + this.#options = options; } - copy(outputStream, options, origin = this.#origin.copy(outputStream, options)) { - return new JsonOutputResponse(origin); + copy(options, outputStream, origin = this.#origin.copy(options, outputStream)) { + return new JsonOutputResponse(origin, options); } update(options) { @@ -14,8 +16,30 @@ module.exports = class JsonOutputResponse { } flush() { + const body = this.#options.body; + + if (body == null) { + return this.#origin.flush(); + } + + if (typeof body === 'string') { + try { + JSON.parse(body); + + return this.#origin + .update({headers: {'Content-Type': 'application/json; charset=utf-8'}}) + .flush(); + + } catch (e) { + return this.#origin.flush(); + } + } + return this.#origin - .update({headers: {'Content-Type': 'application/json; charset=utf-8'}}) + .update({ + headers: {'Content-Type': 'application/json; charset=utf-8'}, + body: JSON.stringify(body) + }) .flush(); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/server/response/LoggedOutputResponse.js b/src/js/server/response/LoggedOutputResponse.js index 3308ead..e512ba9 100644 --- a/src/js/server/response/LoggedOutputResponse.js +++ b/src/js/server/response/LoggedOutputResponse.js @@ -7,7 +7,7 @@ module.exports = class LoggedOutputResponse { this.#logger = logger; } - copy(outputStream, options, logger = this.#logger, origin = this.#origin.copy(outputStream, options)) { + copy(options, outputStream, logger = this.#logger, origin = this.#origin.copy(options, outputStream)) { return new LoggedOutputResponse(origin, logger); } diff --git a/src/js/server/response/OutputResponse.js b/src/js/server/response/OutputResponse.js index 0895d96..e9a1242 100644 --- a/src/js/server/response/OutputResponse.js +++ b/src/js/server/response/OutputResponse.js @@ -1,18 +1,18 @@ module.exports = class OutputResponse { - #outputStream; #options; + #outputStream; - constructor(outputStream, options) { - this.#outputStream = outputStream; + constructor(options, outputStream) { this.#options = {...{statusCode: 200, headers: {}}, ...options}; + this.#outputStream = outputStream; } - copy(outputStream = this.#outputStream, options = this.#options) { - return new OutputResponse(outputStream, {...{statusCode: 200, headers: {}}, ...options}); + copy(options = this.#options, outputStream = this.#outputStream) { + return new OutputResponse({...{statusCode: 200, headers: {}}, ...options}, outputStream); } update(options) { - return new OutputResponse(this.#outputStream, this.#mergeOptions(this.#options, options)); + return new OutputResponse(this.#mergeOptions(this.#options, options), this.#outputStream); } flush() { diff --git a/src/test/e2e/bun/client/client.test.js b/src/test/e2e/bun/client/client.test.js new file mode 100644 index 0000000..72e1ff4 --- /dev/null +++ b/src/test/e2e/bun/client/client.test.js @@ -0,0 +1,114 @@ +/* node:coverage disable */ + +const {describe, test, beforeAll, afterAll} = require('bun:test'); +const assert = require('node:assert'); + + +const http = + // require('node:http'); + require('../../../../js').bun.bunttp; +const requestFunction = http.request; +const { + OutputRequest, + InputResponse +} = require('../../../../js') + .bun + .client; +const { + Server, + Endpoints, + InputRequest, + OutputResponse, +} = require('../../../../js') + .bun + .server; + +const serverConfig = new Server( + new Endpoints([ + { + route() { + return { + method: 'GET', + path: '/test' + }; + }, + handle() { + return { + statusCode: 200 + }; + } + }, + { + route() { + return { + method: 'POST', + path: '/test' + }; + }, + handle() { + return { + statusCode: 201, + body: 'test body' + }; + } + }, + ]), + {port: 8090}, + new InputRequest(), + new OutputResponse(), + http +); + +const request = new OutputRequest(new InputResponse(), requestFunction); + +describe('client', async () => { + let serverInstance; + beforeAll(async () => { + serverInstance = await serverConfig.start(); + }); + afterAll(async () => await serverInstance.stop()); + + await test('should be started', async () => { + await assert.doesNotReject(() => + request.copy( { + url: 'http://localhost:8090', method: 'GET' + }).send(), + {message: 'fetch failed'}); + }); + + await test('should return 501', async () => { + const response = await (request.copy( { + url: 'http://localhost:8090/no_test', method: 'GET' + })).send(); + + assert.strictEqual(response.statusCode(), 501); + assert.strictEqual(response.body().toString(), 'There are no handler for request.'); + }); + + await test('should return 200 and no body', async () => { + const response = await (request.copy( { + url: 'http://localhost:8090/test', method: 'GET' + })).send(); + + assert.strictEqual(response.statusCode(), 200); + assert.strictEqual(response.body().length, 0); + }); + + await test('should return 201 and test body', async () => { + const response = await (request.copy( { + url: 'http://localhost:8090/test', method: 'POST', body: 'test body' + })).send(); + + assert.strictEqual(response.statusCode(), 201); + assert.strictEqual(response.body().toString(), 'test body'); + }); + + await test('should not fall, but body is not a string', async () => { + const response = await (request.copy( { + url: 'http://localhost:8090/test', method: 'POST', body: {} + })).send(); + + assert.strictEqual(response.statusCode(), 201); + assert.strictEqual(response.body().toString(), 'test body'); + }); +}); \ No newline at end of file diff --git a/src/test/e2e/bun/server/json.server.test.js b/src/test/e2e/bun/server/json.server.test.js new file mode 100644 index 0000000..8230d51 --- /dev/null +++ b/src/test/e2e/bun/server/json.server.test.js @@ -0,0 +1,121 @@ +/* node:coverage disable */ + +const {describe, test, beforeAll, afterAll} = require('bun:test'); +const assert = require('node:assert'); + +const http = require('../../../../js').bun.bunttp; +const { + Server, + JsonInputRequest, + JsonOutputResponse, + Endpoints, + InputRequest, + OutputResponse +} = require('../../../../js').bun.server; + +const testBody = {value: 'value', queryValue: 'otherQueryValue'}; + +const serverConfig = new Server( + new Endpoints([ + { + route() { + return { + method: 'GET', + path: '/test' + }; + }, + handle(request) { + return { + statusCode: 200, + body: JSON.stringify({queryKey: request.query().get('queryKey')}) + }; + } + }, + { + route() { + return { + method: 'POST', + path: '/test' + }; + }, + handle(request) { + return { + statusCode: 201, + body: request.body() + }; + } + }, + ]), + {port: 8081}, + new JsonInputRequest(new InputRequest()), + new JsonOutputResponse(new OutputResponse()), + http +); + + +describe('JSON server', async () => { + let serverInstance; + beforeAll(async () => { + serverInstance = await serverConfig.start(); + }); + afterAll(async () => await serverInstance.stop()); + + await test('should be started', async () => { + await assert.doesNotReject(() => fetch('http://localhost:8081'), + {message: 'fetch failed'}); + }); + + await test('should return 501', async () => { + const response = await fetch('http://localhost:8081/test0', + {method: 'GET'}); + const body = await (await response.blob()).text(); + + assert.strictEqual(response.status, 501); + assert.strictEqual(body, 'There are no handler for request.'); + }); + + await test('should return 200 and queryValue in body', async () => { + const response = await fetch('http://localhost:8081/test?queryKey=queryValue', + {method: 'GET'}); + const body = await response.json(); + + assert.strictEqual(response.status, 200); + assert.strictEqual(response.headers.get('Content-Type').includes('application/json'), true); + assert.deepStrictEqual(body, {queryKey: 'queryValue'}); + }); + + await test('should return 400, cause content-type is not application/json', async () => { + const response = await fetch('http://localhost:8081/test', + { + method: 'POST', + headers: {'content-type': 'application/notJson'} + }); + const body = await (await response.blob()).text(); + + assert.strictEqual(response.status, 400); + assert.strictEqual(body, 'Wrong content-type. Only application/json accepted.'); + }); + + await test('should return 400, cause content-type is not set', async () => { + const response = await fetch('http://localhost:8081/test', + {method: 'POST'}); + const body = await (await response.blob()).text(); + + assert.strictEqual(response.status, 400); + assert.strictEqual(body, 'Wrong content-type. Only application/json accepted.'); + }); + + await test('should return 201 and test body', async () => { + const response = await fetch('http://localhost:8081/test?queryKey=queryValue', + { + method: 'POST', + body: JSON.stringify(testBody), + headers: {'content-type': 'application/json'} + }); + const body = await response.json(); + + assert.strictEqual(response.status, 201); + assert.strictEqual(response.headers.get('Content-Type').includes('application/json'), true); + assert.deepStrictEqual(body, testBody); + }); +}); \ No newline at end of file diff --git a/src/test/e2e/bun/server/server.test.js b/src/test/e2e/bun/server/server.test.js new file mode 100644 index 0000000..64e298f --- /dev/null +++ b/src/test/e2e/bun/server/server.test.js @@ -0,0 +1,96 @@ +/* node:coverage disable */ + +const {describe, test, beforeAll, afterAll} = require('bun:test'); +const assert = require('node:assert'); + +const http = require('../../../../js').bun.bunttp; +const { + Server, + Endpoints, + InputRequest, + OutputResponse +} = require('../../../../js').bun.server; + +const testBody = {value: 'value', queryValue: 'otherQueryValue'}; + +const serverConfig = new Server( + new Endpoints([ + { + route() { + return { + method: 'GET', + path: '/test' + }; + }, + handle(request) { + return { + statusCode: 200, + body: request.query().get('queryKey') + }; + } + }, + { + route() { + return { + method: 'POST', + path: '/test' + }; + }, + handle(request) { + return { + statusCode: 201, + body: request.body().toString() + }; + } + }, + ]), + {port: 8080}, + new InputRequest(), + new OutputResponse(), + http +); + + + +describe('server', async () => { + let serverInstance; + beforeAll(async () => { + serverInstance = await serverConfig.start(); + }) + afterAll(async () => await serverInstance.stop()); + + await test('should be started', async () => { + await assert.doesNotReject(() => fetch('http://localhost:8080'), + {message: 'fetch failed'}); + }); + + await test('should return 501', async () => { + const response = await fetch('http://localhost:8080/test0', + {method: 'GET'}); + const body = await (await response.blob()).text(); + + assert.strictEqual(response.status, 501); + assert.strictEqual(body, 'There are no handler for request.'); + }); + + await test('should return 200 and queryValue in body', async () => { + const response = await fetch('http://localhost:8080/test?queryKey=queryValue', + {method: 'GET'}); + const body = await (await response.blob()).text(); + + assert.strictEqual(response.status, 200); + assert.strictEqual(body, 'queryValue'); + }); + + await test('should return 201 and test body', async () => { + const response = await fetch('http://localhost:8080/test?queryKey=queryValue', + { + method: 'POST', + body: JSON.stringify(testBody), + }); + const body = await (await response.blob()).text(); + + assert.strictEqual(response.status, 201); + assert.strictEqual(body, JSON.stringify(testBody)); + }); +}); \ No newline at end of file diff --git a/src/test/e2e/client/client.js b/src/test/e2e/client/client.js index 9476408..ff26558 100644 --- a/src/test/e2e/client/client.js +++ b/src/test/e2e/client/client.js @@ -2,13 +2,14 @@ const {describe, it, before, after} = require('node:test'); const assert = require('node:assert'); -const http = require('node:http'); + +const http = require('node:http'); +const requestFunction = http.request; const { OutputRequest, InputResponse } = require('../../../js/index').client; - const { Server, InputRequest, @@ -17,9 +18,6 @@ const { } = require('../../../js').server; const serverConfig = new Server( - http, - new InputRequest(), - new OutputResponse(), new Endpoints([ { route() { @@ -49,8 +47,12 @@ const serverConfig = new Server( } }, ]), - {port: 8090} + {port: 8090}, + new InputRequest(), + new OutputResponse(), + http ); +const request = new OutputRequest(new InputResponse(), requestFunction); describe('client', async () => { let serverInstance; @@ -61,49 +63,55 @@ describe('client', async () => { await it('should be started', async () => { await assert.doesNotReject(() => - new OutputRequest(http, new InputResponse(), { - url: 'http://localhost', method: 'GET', port: '8090' - }).send(), + request + .copy({ + url: 'http://localhost', method: 'GET', port: '8090' + }).send(), {message: 'fetch failed'}); await assert.doesNotReject(() => - new OutputRequest(http, new InputResponse(), { - port: '8090', method: 'GET', host: 'localhost' - }).send(), + request + .copy({ + port: '8090', method: 'GET', host: 'localhost' + }).send(), {message: 'fetch failed'}); }); await it('should return 501', async () => { - const response = await new OutputRequest(http, new InputResponse(), { - url: 'http://localhost:8090/no_test', method: 'GET' - }).send(); + const response = await (request + .copy({ + url: 'http://localhost:8090/no_test', method: 'GET' + })).send(); assert.strictEqual(response.statusCode(), 501); assert.strictEqual(response.body().toString(), 'There are no handler for request.'); }); await it('should return 200 and no body', async () => { - const response = await new OutputRequest(http, new InputResponse(), { - url: 'http://localhost:8090/test', method: 'GET' - }).send(); + const response = await (request + .copy({ + url: 'http://localhost:8090/test', method: 'GET' + })).send(); assert.strictEqual(response.statusCode(), 200); assert.strictEqual(response.body().length, 0); }); await it('should return 201 and test body', async () => { - const response = await new OutputRequest(http, new InputResponse(), { - url: 'http://localhost:8090/test', method: 'POST', body: 'test body' - }).send(); + const response = await (request + .copy({ + url: 'http://localhost:8090/test', method: 'POST', body: 'test body' + })).send(); assert.strictEqual(response.statusCode(), 201); assert.strictEqual(response.body().toString(), 'test body'); }); await it('should not fall, but body is not a string', async () => { - const response = await new OutputRequest(http, new InputResponse(), { - url: 'http://localhost:8090/test', method: 'POST', body: {} - }).send(); + const response = await (request + .copy({ + url: 'http://localhost:8090/test', method: 'POST', body: {} + })).send(); assert.strictEqual(response.statusCode(), 201); assert.strictEqual(response.body().toString(), 'test body'); diff --git a/src/test/e2e/server/json.server.js b/src/test/e2e/server/json.server.js index 4abaa2b..95bcf03 100644 --- a/src/test/e2e/server/json.server.js +++ b/src/test/e2e/server/json.server.js @@ -15,9 +15,6 @@ const http = require('node:http'); const testBody = {value: 'value', queryValue: 'otherQueryValue'}; const serverConfig = new Server( - http, - new JsonInputRequest(new InputRequest()), - new JsonOutputResponse(new OutputResponse()), new Endpoints([ { route() { @@ -43,12 +40,15 @@ const serverConfig = new Server( handle(request) { return { statusCode: 201, - body: JSON.stringify(request.body()) + body: request.body() }; } }, ]), - {port: 8081} + {port: 8081}, + new JsonInputRequest(new InputRequest()), + new JsonOutputResponse(new OutputResponse()), + http ); @@ -70,7 +70,6 @@ describe('JSON server', async () => { const body = await (await response.blob()).text(); assert.strictEqual(response.status, 501); - assert.strictEqual(response.headers.get('Content-Type').includes('application/json'), true); assert.strictEqual(body, 'There are no handler for request.'); }); @@ -93,7 +92,6 @@ describe('JSON server', async () => { const body = await (await response.blob()).text(); assert.strictEqual(response.status, 400); - assert.strictEqual(response.headers.get('Content-Type').includes('application/json'), true); assert.strictEqual(body, 'Wrong content-type. Only application/json accepted.'); }); @@ -103,7 +101,6 @@ describe('JSON server', async () => { const body = await (await response.blob()).text(); assert.strictEqual(response.status, 400); - assert.strictEqual(response.headers.get('Content-Type').includes('application/json'), true); assert.strictEqual(body, 'Wrong content-type. Only application/json accepted.'); }); diff --git a/src/test/e2e/server/server.js b/src/test/e2e/server/server.js index 4dee8c4..1c3b08b 100644 --- a/src/test/e2e/server/server.js +++ b/src/test/e2e/server/server.js @@ -15,9 +15,6 @@ const { const testBody = {value: 'value', queryValue: 'otherQueryValue'}; const serverConfig = new Server( - http, - new InputRequest(), - new OutputResponse(), new Endpoints([ { route() { @@ -48,7 +45,10 @@ const serverConfig = new Server( } }, ]), - {port: 8080} + {port: 8080}, + new InputRequest(), + new OutputResponse(), + http ); describe('server', async () => { diff --git a/src/test/js/bun/client/request/OutputRequest.test.js b/src/test/js/bun/client/request/OutputRequest.test.js new file mode 100644 index 0000000..b81a04f --- /dev/null +++ b/src/test/js/bun/client/request/OutputRequest.test.js @@ -0,0 +1,175 @@ +/* node:coverage disable */ + +const {describe, test, mock, spyOn, expect, beforeEach, afterEach} = require('bun:test'); +const assert = require('node:assert'); + +const {OutputRequest} = require('../../../../../js').bun.client; + +const testOptions = { + statusCode: 200, + method: 'GET', + headers: {header: 'header'}, + url: 'http://localhost', + body: 'test body' +}; + +const testResponse = { + statusCode: 200, + headers: {header: 'header'}, + body: 'test body' +}; + +const diagnosticResponse = { + copy() { + }, + flush() { + } +}; + +let diagnosticRequestFunction = () => {}; +const diagnosticRequestFunctionOptions = {}; + +function prepareDiagnostic() { + diagnosticResponse.options = {}; + diagnosticResponse.copy = (responseStream) => { + diagnosticResponse.options.responseStream = responseStream; + return diagnosticResponse; + }; + diagnosticResponse.flush = () => { + return testResponse; + } + diagnosticResponse.spy = { + copy: spyOn(diagnosticResponse, 'copy'), + flush: spyOn(diagnosticResponse, 'flush') + } + + diagnosticRequestFunction = mock((url, options) => { + diagnosticRequestFunctionOptions.url = url; + diagnosticRequestFunctionOptions.options = options; + + return testResponse; + }); +} + +function resetDiagnostic() { + diagnosticResponse.spy = null; +} + +describe('OutputRequest', () => { + beforeEach(prepareDiagnostic); + afterEach(resetDiagnostic); + + describe('constructor', () => { + test('should not call anything', () => { + assert.doesNotThrow(() => { + new OutputRequest(); + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, testOptions); + }); + + expect(diagnosticRequestFunction).toHaveBeenCalledTimes(0); + expect(diagnosticResponse.spy.copy).toHaveBeenCalledTimes(0); + expect(diagnosticResponse.spy.flush).toHaveBeenCalledTimes(0); + }); + }); + + describe('copy', () => { + test('should not call anything', () => { + assert.doesNotThrow(() => { + new OutputRequest().copy(); + new OutputRequest(diagnosticResponse, testOptions).copy(); + new OutputRequest().copy(testOptions, diagnosticResponse); + new OutputRequest(diagnosticResponse, testOptions).copy(testOptions, diagnosticResponse); + }); + }); + + test('should return new InputRequest instance', () => { + const inputResponse = new OutputRequest(); + const copyInputResponse = inputResponse.copy(); + + assert.notEqual(inputResponse, copyInputResponse); + assert.strictEqual(typeof inputResponse, typeof copyInputResponse); + }); + }); + + describe('send', () => { + beforeEach(prepareDiagnostic); + afterEach(resetDiagnostic); + + test('should fall, cause options is null', async () => { + await assert.rejects(() => + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, undefined).send(), + {cause: 'INVALID_REQUEST'}); + + expect(diagnosticRequestFunction).toHaveBeenCalledTimes(0); + expect(diagnosticResponse.spy.copy).toHaveBeenCalledTimes(0); + expect(diagnosticResponse.spy.flush).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause requestFunction is null', async () => { + await assert.rejects(() => + new OutputRequest(diagnosticResponse, undefined, testOptions).send(), + {cause: 'INVALID_REQUEST'}); + + expect(diagnosticRequestFunction).toHaveBeenCalledTimes(0); + expect(diagnosticResponse.spy.copy).toHaveBeenCalledTimes(0); + expect(diagnosticResponse.spy.flush).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause requestFunction throws error', async () => { + diagnosticRequestFunction = mock(() => {throw new Error('requestFunction error')}); + + await assert.rejects(() => + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, testOptions).send(), + {message: 'requestFunction error'}); + + expect(diagnosticRequestFunction).toHaveBeenCalledTimes(1); + expect(diagnosticResponse.spy.copy).toHaveBeenCalledTimes(0); + expect(diagnosticResponse.spy.flush).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause response is null', async () => { + await assert.rejects(() => + new OutputRequest(undefined, diagnosticRequestFunction, testOptions).send(), + {cause: 'INVALID_REQUEST'}); + + expect(diagnosticRequestFunction).toHaveBeenCalledTimes(0); + expect(diagnosticResponse.spy.copy).toHaveBeenCalledTimes(0); + expect(diagnosticResponse.spy.flush).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause response.copy throws error', async () => { + diagnosticResponse.copy = () => {throw new Error('response copy error')}; + diagnosticResponse.spy.copy = spyOn(diagnosticResponse, 'copy'); + + await assert.rejects(() => + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, testOptions).send(), + {message: 'response copy error'}); + + expect(diagnosticRequestFunction).toHaveBeenCalledTimes(1); + expect(diagnosticResponse.spy.copy).toHaveBeenCalledTimes(1); + expect(diagnosticResponse.spy.flush).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause response.flush throws error', async () => { + diagnosticResponse.flush = () => {throw new Error('response flush error')}; + diagnosticResponse.spy.flush = spyOn(diagnosticResponse, 'flush'); + + await assert.rejects(() => + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, testOptions).send(), + {message: 'response flush error'}); + + expect(diagnosticRequestFunction).toHaveBeenCalledTimes(1); + expect(diagnosticResponse.spy.copy).toHaveBeenCalledTimes(1); + expect(diagnosticResponse.spy.flush).toHaveBeenCalledTimes(1); + }); + + test('should return response with test options', async () => { + const resultOutputRequest = await new OutputRequest(diagnosticResponse, diagnosticRequestFunction, testOptions).send(); + + assert.deepStrictEqual(resultOutputRequest, testResponse); + assert.deepStrictEqual(diagnosticResponse.options.responseStream, testResponse); + assert.deepStrictEqual(diagnosticRequestFunctionOptions.options, testOptions); + assert.deepStrictEqual(diagnosticRequestFunctionOptions.url, testOptions.url); + }); + }); +}); \ No newline at end of file diff --git a/src/test/js/bun/client/response/InputResponse.test.js b/src/test/js/bun/client/response/InputResponse.test.js new file mode 100644 index 0000000..be2cd31 --- /dev/null +++ b/src/test/js/bun/client/response/InputResponse.test.js @@ -0,0 +1,337 @@ +/* node:coverage disable */ + +const {describe, test, spyOn, expect, beforeEach, afterEach} = require('bun:test'); +const assert = require('node:assert'); + +const {InputResponse} = require('../../../../../js').bun.client; + +const testResponse = { + status: 200, + headers: new Headers({header: 'header'}), + body: 'test body' +}; + +const diagnosticOptions = { + get statusCode() { + return this.diagnosticStatusCode(); + }, + get headers() { + return this.diagnosticHeaders(); + }, + get body() { + return this.diagnosticBody(); + }, + diagnosticStatusCode() { + }, + diagnosticHeaders() { + }, + diagnosticBody() { + } +} + +const diagnosticInputStream = { + get status() { + return this.diagnosticStatus(); + }, + get headers() { + return this.diagnosticHeaders(); + }, + blob() {}, + diagnosticStatus() { + }, + diagnosticHeaders() { + } +}; + +function prepareDiagnostic() { + diagnosticInputStream.diagnosticStatus = () => { + return testResponse.status; + }; + diagnosticInputStream.diagnosticHeaders = () => { + return testResponse.headers; + }; + diagnosticInputStream.blob = () => { + return { + arrayBuffer () { + return testResponse.body; + } + }; + }; + diagnosticInputStream.spy = { + diagnosticStatus: spyOn(diagnosticInputStream, 'diagnosticStatus'), + diagnosticHeaders: spyOn(diagnosticInputStream, 'diagnosticHeaders'), + blob: spyOn(diagnosticInputStream, 'blob'), + }; + + diagnosticOptions.diagnosticStatusCode = () => { + return testResponse.status; + }; + diagnosticOptions.diagnosticHeaders = () => { + return testResponse.headers; + }; + diagnosticOptions.diagnosticBody = () => { + return testResponse.body; + }; + diagnosticOptions.spy = { + diagnosticStatusCode: spyOn(diagnosticOptions, 'diagnosticStatusCode'), + diagnosticHeaders: spyOn(diagnosticOptions, 'diagnosticHeaders'), + diagnosticBody: spyOn(diagnosticOptions, 'diagnosticBody'), + } +} + +function resetDiagnostic() { + diagnosticInputStream.spy = {}; + diagnosticOptions.spy = {}; +} + +describe('InputResponse', () => { + beforeEach(prepareDiagnostic); + afterEach(resetDiagnostic); + + describe('constructor', () => { + test('should not call anything', () => { + assert.doesNotThrow(() => { + new InputResponse(); + new InputResponse(diagnosticInputStream, testResponse); + new InputResponse(undefined, testResponse); + new InputResponse(diagnosticInputStream); + }); + }); + }); + + describe('copy', () => { + test('should not call anything', () => { + assert.doesNotThrow(() => new InputResponse().copy()); + }); + + test('should return new InputResponse instance', () => { + const inputResponse = new InputResponse(); + const copyInputResponse = inputResponse.copy(); + + assert.notEqual(inputResponse, copyInputResponse); + assert.strictEqual(typeof inputResponse, typeof copyInputResponse); + }); + }); + + describe('flush', () => { + test('should fall on inputStream, cause null', async () => { + await assert.rejects(() => new InputResponse().flush(), + {cause: 'INVALID_RESPONSE'}); + + expect(diagnosticInputStream.spy.diagnosticStatus).toHaveBeenCalledTimes(0); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(0); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(0); + }); + + test('should fall on inputStream, cause status throws error', async () => { + diagnosticInputStream.diagnosticStatus = () => {throw new Error('status error')}; + diagnosticInputStream.spy.diagnosticStatus = spyOn(diagnosticInputStream, 'diagnosticStatus'); + + await assert.rejects(() => new InputResponse(diagnosticInputStream).flush(), + {message: 'status error', cause: 'INVALID_RESPONSE'}); + + expect(diagnosticInputStream.spy.diagnosticStatus).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(0); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(0); + }); + + test('should fall on inputStream, cause headers throws error', async () => { + diagnosticInputStream.diagnosticHeaders = () => {throw new Error('headers error')}; + diagnosticInputStream.spy.diagnosticHeaders = spyOn(diagnosticInputStream, 'diagnosticHeaders'); + + await assert.rejects(() => new InputResponse(diagnosticInputStream).flush(), + {message: 'headers error', cause: 'INVALID_RESPONSE'}); + + expect(diagnosticInputStream.spy.diagnosticStatus).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(0); + }); + + test('should fall on inputStream, cause blob throws error', async () => { + diagnosticInputStream.blob = () => {throw new Error('blob error')}; + diagnosticInputStream.spy.blob = spyOn(diagnosticInputStream, 'blob'); + + await assert.rejects(() => new InputResponse(diagnosticInputStream).flush(), + {message: 'blob error', cause: 'INVALID_RESPONSE'}); + + expect(diagnosticInputStream.spy.diagnosticStatus).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(1); + }); + + test('should fall on inputStream, cause blob.arrayBuffer throws error', async () => { + diagnosticInputStream.blob = () => { + return { + arrayBuffer () { + throw new Error('arrayBuffer error'); + } + }; + }; + diagnosticInputStream.spy.blob = spyOn(diagnosticInputStream, 'blob'); + + await assert.rejects(() => new InputResponse(diagnosticInputStream).flush(), + {message: 'arrayBuffer error', cause: 'INVALID_RESPONSE'}); + + expect(diagnosticInputStream.spy.diagnosticStatus).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(1); + }); + + test('should fall cause blob.arrayBuffer is empty', async () => { + diagnosticInputStream.blob = () => { + return { + arrayBuffer () { + return null; + } + }; + }; + diagnosticInputStream.spy.blob = spyOn(diagnosticInputStream, 'blob'); + + await assert.rejects(() => new InputResponse(diagnosticInputStream).flush(), + {cause: 'INVALID_RESPONSE'}); + + expect(diagnosticInputStream.spy.diagnosticStatus).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(1); + }); + + test('should not fall', async () => { + await assert.doesNotReject(() => new InputResponse(diagnosticInputStream).flush()); + + expect(diagnosticInputStream.spy.diagnosticStatus).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(1); + }); + + test('should return another InputResponse', async () => { + const inputResponse = new InputResponse(diagnosticInputStream); + const resultInputResponse = await inputResponse.flush(); + + assert.notEqual(inputResponse, resultInputResponse); + }); + + test('should return InputResponse with body', async () => { + const resultInputResponse = await new InputResponse(diagnosticInputStream).flush(); + + expect(diagnosticInputStream.spy.diagnosticStatus).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(1); + + assert.strictEqual(resultInputResponse.statusCode(), testResponse.status); + assert.deepStrictEqual(resultInputResponse.headers(), new Headers(testResponse.headers)); + assert.deepStrictEqual(resultInputResponse.body(), Buffer.from(testResponse.body)); + }); + + test('should return InputResponse with empty body', async () => { + diagnosticInputStream.blob = () => { + return { + arrayBuffer () { + return []; + } + }; + }; + diagnosticInputStream.spy.blob = spyOn(diagnosticInputStream, 'blob'); + + const resultInputResponse = await new InputResponse(diagnosticInputStream).flush(); + + expect(diagnosticInputStream.spy.diagnosticStatus).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(1); + + assert.strictEqual(resultInputResponse.statusCode(), testResponse.status); + assert.deepStrictEqual(resultInputResponse.headers(), new Headers(testResponse.headers)); + assert.deepStrictEqual(resultInputResponse.body(), Buffer.from([])); + assert.deepStrictEqual(resultInputResponse.body().length, 0); + }); + }); + + describe('statusCode', () => { + test('should not fall', () => { + assert.doesNotThrow(() => new InputResponse(undefined, diagnosticOptions).statusCode()); + + expect(diagnosticOptions.spy.diagnosticStatusCode).toHaveBeenCalledTimes(1); + }); + + test('should fall, cause null', () => { + assert.throws(() => new InputResponse().statusCode(), {name: 'TypeError'}); + + expect(diagnosticOptions.spy.diagnosticStatusCode).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause error', () => { + diagnosticOptions.diagnosticStatusCode = () => { + throw new Error('statusCode error'); + }; + diagnosticOptions.spy.diagnosticStatusCode = spyOn(diagnosticOptions, 'diagnosticStatusCode'); + + assert.throws(() => new InputResponse(undefined, diagnosticOptions).statusCode(), {message: 'statusCode error'}); + expect(diagnosticOptions.spy.diagnosticStatusCode).toHaveBeenCalledTimes(1); + }); + + test('should return test statusCode value', () => { + const resultStatusCode = new InputResponse(undefined, diagnosticOptions).statusCode(); + + assert.strictEqual(resultStatusCode, testResponse.status); + }); + }); + + describe('headers', () => { + test('should not fall', () => { + assert.doesNotThrow(() => new InputResponse(undefined, diagnosticOptions).headers()); + + expect(diagnosticOptions.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + }); + + test('should fall, cause null', () => { + assert.throws(() => new InputResponse().headers(), {name: 'TypeError'}); + + expect(diagnosticOptions.spy.diagnosticHeaders).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause error', () => { + diagnosticOptions.diagnosticHeaders = () => { + throw new Error('headers error'); + }; + diagnosticOptions.spy.diagnosticHeaders = spyOn(diagnosticOptions, 'diagnosticHeaders'); + + assert.throws(() => new InputResponse(undefined, diagnosticOptions).headers(), {message: 'headers error'}); + expect(diagnosticOptions.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + }); + + test('should return test headers value', () => { + const resultHeaders = new InputResponse(undefined, diagnosticOptions).headers(); + + assert.strictEqual(resultHeaders, testResponse.headers); + }); + }); + + describe('body', () => { + test('should not fall', () => { + assert.doesNotThrow(() => new InputResponse(undefined, diagnosticOptions).body()); + + expect(diagnosticOptions.spy.diagnosticBody).toHaveBeenCalledTimes(1); + }); + + test('should fall, cause null', () => { + assert.throws(() => new InputResponse().body(), {name: 'TypeError'}); + + expect(diagnosticOptions.spy.diagnosticBody).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause error', () => { + diagnosticOptions.diagnosticBody = () => { + throw new Error('body error'); + }; + diagnosticOptions.spy.diagnosticBody = spyOn(diagnosticOptions, 'diagnosticBody'); + + assert.throws(() => new InputResponse(undefined, diagnosticOptions).body(), {message: 'body error'}); + expect(diagnosticOptions.spy.diagnosticBody).toHaveBeenCalledTimes(1); + }); + + test('should return test body value', () => { + const resultBody = new InputResponse(undefined, diagnosticOptions).body(); + + assert.strictEqual(resultBody, testResponse.body); + }); + }); +}); \ No newline at end of file diff --git a/src/test/js/bun/server/request/InputRequest.test.js b/src/test/js/bun/server/request/InputRequest.test.js new file mode 100644 index 0000000..fd8526f --- /dev/null +++ b/src/test/js/bun/server/request/InputRequest.test.js @@ -0,0 +1,471 @@ +/* node:coverage disable */ + +const {describe, test, spyOn, expect, beforeEach, afterEach} = require('bun:test'); +const assert = require('node:assert'); + +const {InputRequest} = require('../../../../../js').bun.server; + +const testInputStream = { + method: 'GET', + url: 'http://localhost/test?query=test', + headers: new Headers({header: 'header'}), + body: 'test body' +}; + +const testOptions = { + method: 'GET', + path: '/test', + query: 'query=test', + headers: {header: 'header'}, + body: 'test body', +}; + +const diagnosticInputStream = { + get method() { + return this.diagnosticMethod(); + }, + get url() { + return this.diagnosticUrl(); + }, + get headers() { + return this.diagnosticHeaders(); + }, + blob() {}, + diagnosticMethod() { + }, + diagnosticUrl() { + }, + diagnosticHeaders() { + } +}; + +const diagnosticOptions = { + get method() { + return this.diagnosticMethod(); + }, + get path() { + return this.diagnosticPath(); + }, + get headers() { + return this.diagnosticHeaders(); + }, + get body() { + return this.diagnosticBody(); + }, + get query() { + return this.diagnosticQuery(); + }, + diagnosticMethod() { + }, + diagnosticPath() { + }, + diagnosticHeaders() { + }, + diagnosticBody() { + }, + diagnosticQuery() { + } +}; + +function prepareDiagnostic() { + diagnosticInputStream.diagnosticMethod = () => { + return testInputStream.method; + }; + diagnosticInputStream.diagnosticUrl = () => { + return testInputStream.url; + }; + diagnosticInputStream.diagnosticHeaders = () => { + return testInputStream.headers; + }; + diagnosticInputStream.blob = () => { + return { + arrayBuffer () { + return testInputStream.body; + } + }; + }; + diagnosticInputStream.spy = { + diagnosticMethod: spyOn(diagnosticInputStream, 'diagnosticMethod'), + diagnosticUrl: spyOn(diagnosticInputStream, 'diagnosticUrl'), + diagnosticHeaders: spyOn(diagnosticInputStream, 'diagnosticHeaders'), + blob: spyOn(diagnosticInputStream, 'blob'), + }; + + diagnosticOptions.diagnosticMethod = () => { + return testOptions.method; + }; + diagnosticOptions.diagnosticPath = () => { + return testOptions.path; + }; + diagnosticOptions.diagnosticHeaders = () => { + return testOptions.headers; + }; + diagnosticOptions.diagnosticBody = () => { + return testOptions.body; + }; + diagnosticOptions.diagnosticQuery = () => { + return testOptions.query; + }; + diagnosticOptions.spy = { + diagnosticMethod: spyOn(diagnosticOptions, 'diagnosticMethod'), + diagnosticPath: spyOn(diagnosticOptions, 'diagnosticPath'), + diagnosticHeaders: spyOn(diagnosticOptions, 'diagnosticHeaders'), + diagnosticBody: spyOn(diagnosticOptions, 'diagnosticBody'), + diagnosticQuery: spyOn(diagnosticOptions, 'diagnosticQuery'), + } +} + +function resetDiagnostic() { + diagnosticInputStream.spy = {}; + diagnosticOptions.spy = {}; +} + +describe('InputResponse', () => { + beforeEach(prepareDiagnostic); + afterEach(resetDiagnostic); + + describe('constructor', () => { + test('should not call anything', () => { + assert.doesNotThrow(() => { + new InputRequest(); + new InputRequest(diagnosticInputStream, testOptions); + new InputRequest(undefined, testOptions); + new InputRequest(diagnosticInputStream); + }); + }); + }); + + describe('copy', () => { + test('should not call anything', () => { + assert.doesNotThrow(() => new InputRequest().copy()); + }); + + test('should return new InputResponse instance', () => { + const inputRequest = new InputRequest(); + const copyInputRequest = inputRequest.copy(); + + assert.notEqual(inputRequest, copyInputRequest); + assert.strictEqual(typeof inputRequest, typeof copyInputRequest); + }); + }); + + describe('flush', () => { + test('should fall on inputStream, cause null', async () => { + await assert.rejects(() => new InputRequest().flush(), + {cause: 'INVALID_REQUEST'}); + + expect(diagnosticInputStream.spy.diagnosticMethod).toHaveBeenCalledTimes(0); + expect(diagnosticInputStream.spy.diagnosticUrl).toHaveBeenCalledTimes(0); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(0); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(0); + }); + + test('should fall on inputStream, cause method throws error', async () => { + diagnosticInputStream.diagnosticMethod = () => {throw new Error('method error')}; + diagnosticInputStream.spy.diagnosticMethod = spyOn(diagnosticInputStream, 'diagnosticMethod'); + + await assert.rejects(() => new InputRequest(diagnosticInputStream).flush(), + {message: 'method error', cause: 'INVALID_REQUEST'}); + + expect(diagnosticInputStream.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticUrl).toHaveBeenCalledTimes(0); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(0); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(0); + }); + + test('should fall on inputStream, cause url throws error', async () => { + diagnosticInputStream.diagnosticUrl = () => {throw new Error('url error')}; + diagnosticInputStream.spy.diagnosticUrl = spyOn(diagnosticInputStream, 'diagnosticUrl'); + + await assert.rejects(() => new InputRequest(diagnosticInputStream).flush(), + {message: 'url error', cause: 'INVALID_REQUEST'}); + + expect(diagnosticInputStream.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticUrl).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(0); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(0); + }); + + test('should fall on inputStream, cause headers throws error', async () => { + diagnosticInputStream.diagnosticHeaders = () => {throw new Error('headers error')}; + diagnosticInputStream.spy.diagnosticHeaders = spyOn(diagnosticInputStream, 'diagnosticHeaders'); + + await assert.rejects(() => new InputRequest(diagnosticInputStream).flush(), + {message: 'headers error', cause: 'INVALID_REQUEST'}); + + expect(diagnosticInputStream.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticUrl).toHaveBeenCalledTimes(2); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(0); + }); + + test('should fall on inputStream, cause blob throws error', async () => { + diagnosticInputStream.blob = () => {throw new Error('blob error')}; + diagnosticInputStream.spy.blob = spyOn(diagnosticInputStream, 'blob'); + + await assert.rejects(() => new InputRequest(diagnosticInputStream).flush(), + {message: 'blob error', cause: 'INVALID_REQUEST'}); + + expect(diagnosticInputStream.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticUrl).toHaveBeenCalledTimes(2); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(1); + }); + + test('should fall on inputStream, cause blob.arrayBuffer throws error', async () => { + diagnosticInputStream.blob = () => { + return { + arrayBuffer () { + throw new Error('arrayBuffer error'); + } + }; + }; + diagnosticInputStream.spy.blob = spyOn(diagnosticInputStream, 'blob'); + + await assert.rejects(() => new InputRequest(diagnosticInputStream).flush(), + {message: 'arrayBuffer error', cause: 'INVALID_REQUEST'}); + + expect(diagnosticInputStream.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticUrl).toHaveBeenCalledTimes(2); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(1); + }); + + test('should fall cause blob.arrayBuffer is empty', async () => { + diagnosticInputStream.blob = () => { + return { + arrayBuffer () { + return null; + } + }; + }; + diagnosticInputStream.spy.blob = spyOn(diagnosticInputStream, 'blob'); + + await assert.rejects(() => new InputRequest(diagnosticInputStream).flush(), + {cause: 'INVALID_REQUEST'}); + + expect(diagnosticInputStream.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticUrl).toHaveBeenCalledTimes(2); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(1); + }); + + test('should not fall', async () => { + await assert.doesNotReject(() => new InputRequest(diagnosticInputStream).flush()); + + expect(diagnosticInputStream.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticUrl).toHaveBeenCalledTimes(2); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(1); + }); + + test('should return another InputResponse', async () => { + const inputRequest = new InputRequest(diagnosticInputStream); + const resultInputRequest = await inputRequest.flush(); + + assert.notEqual(inputRequest, resultInputRequest); + }); + + test('should return InputResponse with body', async () => { + const resultInputRequest = await new InputRequest(diagnosticInputStream).flush(); + + expect(diagnosticInputStream.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticUrl).toHaveBeenCalledTimes(2); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(1); + + assert.deepStrictEqual(resultInputRequest.route(), {method: testInputStream.method, path: new URL(testInputStream.url).pathname}); + assert.deepStrictEqual(resultInputRequest.query(), new URLSearchParams(testInputStream.url)); + assert.deepStrictEqual(resultInputRequest.headers(), testInputStream.headers); + assert.deepStrictEqual(resultInputRequest.body(), Buffer.from(testInputStream.body)); + }); + + test('should return InputResponse with empty body', async () => { + diagnosticInputStream.blob = () => { + return { + arrayBuffer () { + return []; + } + }; + }; + diagnosticInputStream.spy.blob = spyOn(diagnosticInputStream, 'blob'); + + const resultInputRequest = await new InputRequest(diagnosticInputStream).flush(); + + expect(diagnosticInputStream.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.diagnosticUrl).toHaveBeenCalledTimes(2); + expect(diagnosticInputStream.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + expect(diagnosticInputStream.spy.blob).toHaveBeenCalledTimes(1); + + assert.deepStrictEqual(resultInputRequest.route(), {method: testInputStream.method, path: new URL(testInputStream.url).pathname}); + assert.deepStrictEqual(resultInputRequest.query(), new URLSearchParams(testInputStream.url)); + assert.deepStrictEqual(resultInputRequest.headers(), testInputStream.headers); + assert.deepStrictEqual(resultInputRequest.body(), Buffer.from([])); + assert.deepStrictEqual(resultInputRequest.body().length, 0); + }); + }); + + describe('route', () => { + test('should not fall', () => { + assert.doesNotThrow(() => new InputRequest(undefined, diagnosticOptions).route()); + + expect(diagnosticOptions.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticOptions.spy.diagnosticPath).toHaveBeenCalledTimes(1); + }); + + test('should fall, cause null', () => { + assert.throws(() => new InputRequest().route(), + {name: 'TypeError'}); + + expect(diagnosticOptions.spy.diagnosticMethod).toHaveBeenCalledTimes(0); + expect(diagnosticOptions.spy.diagnosticPath).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause method is null', () => { + diagnosticOptions.diagnosticMethod = () => {return null}; + diagnosticOptions.spy.diagnosticMethod = spyOn(diagnosticOptions, 'diagnosticMethod'); + + assert.throws(() => new InputRequest(undefined, diagnosticOptions).route(), + {name: 'TypeError'}); + + expect(diagnosticOptions.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticOptions.spy.diagnosticPath).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause path is null', () => { + diagnosticOptions.diagnosticPath = () => {return null}; + diagnosticOptions.spy.diagnosticPath = spyOn(diagnosticOptions, 'diagnosticPath'); + + assert.throws(() => new InputRequest(undefined, diagnosticOptions).route(), + {name: 'TypeError'}); + + expect(diagnosticOptions.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticOptions.spy.diagnosticPath).toHaveBeenCalledTimes(1); + }); + + test('should fall, cause method throws error', () => { + diagnosticOptions.diagnosticMethod = () => {throw new Error('method error')}; + diagnosticOptions.spy.diagnosticMethod = spyOn(diagnosticOptions, 'diagnosticMethod'); + + assert.throws(() => new InputRequest(undefined, diagnosticOptions).route(), + {message: 'method error'}); + + expect(diagnosticOptions.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticOptions.spy.diagnosticPath).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause path throws error', () => { + diagnosticOptions.diagnosticPath = () => {throw new Error('path error')}; + diagnosticOptions.spy.diagnosticPath = spyOn(diagnosticOptions, 'diagnosticPath'); + + assert.throws(() => new InputRequest(undefined, diagnosticOptions).route(), + {message: 'path error'}); + + expect(diagnosticOptions.spy.diagnosticMethod).toHaveBeenCalledTimes(1); + expect(diagnosticOptions.spy.diagnosticPath).toHaveBeenCalledTimes(1); + }); + + test('should return route value in right cases', () => { + const resultRoute = new InputRequest(undefined, diagnosticOptions).route(); + + assert.deepStrictEqual(resultRoute, { + path: testOptions.path.toString().toLowerCase(), + method: testOptions.method.toString().toUpperCase() + }); + }); + }); + + describe('query', () => { + test('should not fall', () => { + assert.doesNotThrow(() => new InputRequest(undefined, diagnosticOptions).query()); + + expect(diagnosticOptions.spy.diagnosticQuery).toHaveBeenCalledTimes(1); + }); + + test('should fall, cause null', () => { + assert.throws(() => new InputRequest().query(), {name: 'TypeError'}); + + expect(diagnosticOptions.spy.diagnosticQuery).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause error', () => { + diagnosticOptions.diagnosticQuery = () => { + throw new Error('query error'); + }; + diagnosticOptions.spy.diagnosticQuery = spyOn(diagnosticOptions, 'diagnosticQuery'); + + assert.throws(() => new InputRequest(undefined, diagnosticOptions).query(), + {message: 'query error'}); + + expect(diagnosticOptions.spy.diagnosticQuery).toHaveBeenCalledTimes(1); + }); + + test('should return test query value', () => { + const resultQuery = new InputRequest(undefined, diagnosticOptions).query(); + + assert.strictEqual(resultQuery, testOptions.query); + }); + }); + + describe('headers', () => { + test('should not fall', () => { + assert.doesNotThrow(() => new InputRequest(undefined, diagnosticOptions).headers()); + + expect(diagnosticOptions.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + }); + + test('should fall, cause null', () => { + assert.throws(() => new InputRequest().headers(), {name: 'TypeError'}); + + expect(diagnosticOptions.spy.diagnosticHeaders).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause error', () => { + diagnosticOptions.diagnosticHeaders = () => { + throw new Error('headers error'); + }; + diagnosticOptions.spy.diagnosticHeaders = spyOn(diagnosticOptions, 'diagnosticHeaders'); + + assert.throws(() => new InputRequest(undefined, diagnosticOptions).headers(), + {message: 'headers error'}); + expect(diagnosticOptions.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + }); + + test('should return test headers value', () => { + const resultHeaders = new InputRequest(undefined, diagnosticOptions).headers(); + + assert.strictEqual(resultHeaders, testOptions.headers); + }); + }); + + describe('body', () => { + test('should not fall', () => { + assert.doesNotThrow(() => new InputRequest(undefined, diagnosticOptions).body()); + + expect(diagnosticOptions.spy.diagnosticBody).toHaveBeenCalledTimes(1); + }); + + test('should fall, cause null', () => { + assert.throws(() => new InputRequest().body(), {name: 'TypeError'}); + + expect(diagnosticOptions.spy.diagnosticBody).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause error', () => { + diagnosticOptions.diagnosticBody = () => { + throw new Error('body error'); + }; + diagnosticOptions.spy.diagnosticBody = spyOn(diagnosticOptions, 'diagnosticBody'); + + assert.throws(() => new InputRequest(undefined, diagnosticOptions).body(), + {message: 'body error'}); + expect(diagnosticOptions.spy.diagnosticBody).toHaveBeenCalledTimes(1); + }); + + test('should return test body value', () => { + const resultBody = new InputRequest(undefined, diagnosticOptions).body(); + + assert.strictEqual(resultBody, testOptions.body); + }); + }); +}); \ No newline at end of file diff --git a/src/test/js/bun/server/response/OutputResponse.test.js b/src/test/js/bun/server/response/OutputResponse.test.js new file mode 100644 index 0000000..50aa797 --- /dev/null +++ b/src/test/js/bun/server/response/OutputResponse.test.js @@ -0,0 +1,196 @@ +/* node:coverage disable */ + +const {describe, test, spyOn, expect, beforeEach, afterEach} = require('bun:test'); +const assert = require('node:assert'); + +const {OutputResponse} = require('../../../../../js/').bun.server; + + +const testOptions = { + statusCode: 200, + headers: {header: 'header'}, + body: 'test' +}; + +const diagnosticOptions = { + get statusCode() { + return this.diagnosticStatusCode(); + }, + get headers() { + return this.diagnosticHeaders(); + }, + get body() { + return this.diagnosticBody(); + }, + diagnosticStatusCode() { + }, + diagnosticHeaders() { + }, + diagnosticBody() { + } +}; + +function prepareDiagnostic() { + diagnosticOptions.diagnosticStatusCode = () => { + return testOptions.status; + }; + diagnosticOptions.diagnosticHeaders = () => { + return testOptions.headers; + }; + diagnosticOptions.diagnosticBody = () => { + return testOptions.body; + }; + diagnosticOptions.spy = { + diagnosticStatusCode: spyOn(diagnosticOptions, 'diagnosticStatusCode'), + diagnosticHeaders: spyOn(diagnosticOptions, 'diagnosticHeaders'), + diagnosticBody: spyOn(diagnosticOptions, 'diagnosticBody'), + }; +} + +function resetDiagnostic() { + diagnosticOptions.spy = {}; +} + +describe('OutputResponse', () => { + beforeEach(prepareDiagnostic); + afterEach(resetDiagnostic); + + describe('constructor', () => { + test('should not call anything', () => { + assert.doesNotThrow(() => { + new OutputResponse(); + new OutputResponse(testOptions); + }); + }); + }); + + describe('copy', () => { + test('should not call anything', () => { + assert.doesNotThrow(() => { + new OutputResponse().copy(); + }); + }); + + test('should return new OutputResponse instance', () => { + const outputResponse = new OutputResponse(); + const copyOutputResponse = outputResponse.copy(); + + assert.notEqual(outputResponse, copyOutputResponse); + assert.strictEqual(typeof outputResponse, typeof copyOutputResponse); + }); + }); + + describe('flush', () => { + beforeEach(prepareDiagnostic); + afterEach(resetDiagnostic); + + test('should be equal with input options', () => { + const resultedOutputStream = new OutputResponse(testOptions).flush(); + + assert.deepStrictEqual(resultedOutputStream, + new Response(testOptions.body, { + status: testOptions.statusCode, + headers: testOptions.headers + })); + }); + + test('should be equal with default options', () => { + const resultedOutputStream = new OutputResponse().flush(); + + assert.deepStrictEqual(resultedOutputStream, + new Response(undefined, { + status: 200, + headers: {} + })); + }); + + test('should fall, cause statusCode throws error', () => { + diagnosticOptions.diagnosticStatusCode = () => { + throw new Error('statusCode error'); + }; + diagnosticOptions.spy.diagnosticStatusCode = spyOn(diagnosticOptions, 'diagnosticStatusCode'); + + assert.throws(() => new OutputResponse(diagnosticOptions).flush(), + {message: 'statusCode error'}); + + expect(diagnosticOptions.spy.diagnosticBody).toHaveBeenCalledTimes(0); + expect(diagnosticOptions.spy.diagnosticStatusCode).toHaveBeenCalledTimes(1); + expect(diagnosticOptions.spy.diagnosticHeaders).toHaveBeenCalledTimes(0); + }); + + test('should fall, cause headers throws error', () => { + diagnosticOptions.diagnosticHeaders = () => { + throw new Error('headers error'); + }; + diagnosticOptions.spy.diagnosticHeaders = spyOn(diagnosticOptions, 'diagnosticHeaders'); + + assert.throws(() => new OutputResponse(diagnosticOptions).flush(), + {message: 'headers error'}); + + expect(diagnosticOptions.spy.diagnosticBody).toHaveBeenCalledTimes(0); + expect(diagnosticOptions.spy.diagnosticStatusCode).toHaveBeenCalledTimes(1); + expect(diagnosticOptions.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + }); + + test('should fall, cause body throws error', () => { + diagnosticOptions.diagnosticBody = () => { + throw new Error('body error'); + }; + diagnosticOptions.spy.diagnosticBody = spyOn(diagnosticOptions, 'diagnosticBody'); + + assert.throws(() => new OutputResponse(diagnosticOptions).flush(), + {message: 'body error'}); + + expect(diagnosticOptions.spy.diagnosticBody).toHaveBeenCalledTimes(1); + expect(diagnosticOptions.spy.diagnosticStatusCode).toHaveBeenCalledTimes(1); + expect(diagnosticOptions.spy.diagnosticHeaders).toHaveBeenCalledTimes(1); + }); + }); + + describe('update', () => { + beforeEach(prepareDiagnostic); + afterEach(resetDiagnostic); + + test('should be default', () => { + const resultedOutputStream = new OutputResponse() + .update().flush(); + + assert.deepStrictEqual(resultedOutputStream, + new Response(undefined, { + status: 200, + headers: {} + })); + }); + + test('should be updated by statusCode, body and headers', () => { + const resultedOutputStream = new OutputResponse() + .update(testOptions).flush(); + + assert.deepStrictEqual(resultedOutputStream, + new Response(testOptions.body, { + status: testOptions.statusCode, + headers: testOptions.headers + })); + }); + + test('should be equal before and after update', () => { + const beforeUpdateResultedOutputStream = new OutputResponse(testOptions).flush(); + const afterUpdateResultedOutputStream = new OutputResponse().update(testOptions).flush(); + + assert.deepStrictEqual(beforeUpdateResultedOutputStream, + new Response(testOptions.body, { + status: testOptions.statusCode, + headers: testOptions.headers + })); + assert.deepStrictEqual(beforeUpdateResultedOutputStream, afterUpdateResultedOutputStream); + }); + + test('should not be updated by null or undefined option param', () => { + const beforeUpdateResultedOutputStream = new OutputResponse(testOptions).flush(); + const afterUpdateResultedOutputStream = new OutputResponse(testOptions) + .update({statusCode: null, headers: undefined}).flush(); + + assert.deepStrictEqual(beforeUpdateResultedOutputStream, afterUpdateResultedOutputStream); + }); + }); +}); \ No newline at end of file diff --git a/src/test/js/client/request/OutputRequest.js b/src/test/js/client/request/OutputRequest.js index d010943..7f1893a 100644 --- a/src/test/js/client/request/OutputRequest.js +++ b/src/test/js/client/request/OutputRequest.js @@ -19,10 +19,8 @@ const diagnosticResponse = { } }; -const diagnosticHttp = { - request() { - } -}; +let diagnosticRequestFunction = () => {} +const diagnosticRequestFunctionOptions = {} const diagnosticOutputStream = { write() { @@ -43,21 +41,18 @@ function prepareDiagnostic() { mock.method(diagnosticOutputStream, 'write'); mock.method(diagnosticOutputStream, 'end'); - diagnosticHttp.options = {}; - diagnosticHttp.request = (url, options, cb) => { + diagnosticRequestFunction = mock.fn((url, options, cb) => { if (typeof url === 'object') { cb = options; options = url; url = undefined; } - diagnosticHttp.options.url = url; - diagnosticHttp.options.options = options; + diagnosticRequestFunctionOptions.url = url; + diagnosticRequestFunctionOptions.options = options; setTimeout(() => cb(testOptions), 0); return diagnosticOutputStream; - }; - - mock.method(diagnosticHttp, 'request'); + }); diagnosticResponse.options = {}; diagnosticResponse.copy = (options) => { @@ -84,11 +79,11 @@ describe('OutputRequest', () => { it('should not call anything', () => { assert.doesNotThrow(() => { new OutputRequest(); - new OutputRequest(diagnosticHttp, diagnosticResponse, testOptions); + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, testOptions); }); assert.strictEqual(diagnosticResponse.copy.mock.calls.length, 0); - assert.strictEqual(diagnosticHttp.request.mock.calls.length, 0); + assert.strictEqual(diagnosticRequestFunction.mock.calls.length, 0); }); }); @@ -96,9 +91,10 @@ describe('OutputRequest', () => { it('should not call anything', () => { assert.doesNotThrow(() => { new OutputRequest().copy(); - new OutputRequest(diagnosticHttp, diagnosticResponse, testOptions).copy(); - new OutputRequest().copy(testOptions, diagnosticResponse, diagnosticHttp); - new OutputRequest(diagnosticHttp, diagnosticResponse, testOptions).copy(testOptions, diagnosticResponse, diagnosticHttp); + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, testOptions).copy(); + new OutputRequest().copy(testOptions, diagnosticResponse, diagnosticRequestFunction); + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, testOptions) + .copy(testOptions, diagnosticResponse, diagnosticRequestFunction); }); }); @@ -112,23 +108,22 @@ describe('OutputRequest', () => { }); describe('send', () => { - it('should fall, cause http is null', async () => { - await assert.rejects(() => new OutputRequest(undefined).send(), + it('should fall, cause requestFunction is null', async () => { + await assert.rejects(() => new OutputRequest(diagnosticResponse,undefined).send(), {cause: 'INVALID_REQUEST'}); - assert.strictEqual(diagnosticHttp.request.mock.calls.length, 0); + assert.strictEqual(diagnosticRequestFunction.mock.calls.length, 0); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 0); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 0); }); - it('should fall, cause http throws error ', async () => { - diagnosticHttp.request = () => {throw new Error('http error')}; - mock.method(diagnosticHttp, 'request'); + it('should fall, cause requestFunction throws error ', async () => { + diagnosticRequestFunction = mock.fn(() => {throw new Error('requestFunction error')}); - await assert.rejects(() => new OutputRequest(diagnosticHttp, undefined, {}).send(), - {message: 'http error'}); + await assert.rejects(() => new OutputRequest(diagnosticResponse, diagnosticRequestFunction, {}).send(), + {message: 'requestFunction error'}); - assert.strictEqual(diagnosticHttp.request.mock.calls.length, 1); + assert.strictEqual(diagnosticRequestFunction.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 0); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 0); }); @@ -138,10 +133,10 @@ describe('OutputRequest', () => { mock.method(diagnosticOutputStream, 'end'); await assert.rejects(() => - new OutputRequest(diagnosticHttp, diagnosticResponse, {method: 'GET'}).send(), + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, {method: 'GET'}).send(), {message: 'request end error'}); - assert.strictEqual(diagnosticHttp.request.mock.calls.length, 1); + assert.strictEqual(diagnosticRequestFunction.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 0); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 1); }); @@ -151,30 +146,30 @@ describe('OutputRequest', () => { mock.method(diagnosticOutputStream, 'write'); await assert.rejects(() => - new OutputRequest(diagnosticHttp, diagnosticResponse, {method: 'POST', body: 'test body'}) + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, {method: 'POST', body: 'test body'}) .send(), {message: 'request write error'}); - assert.strictEqual(diagnosticHttp.request.mock.calls.length, 1); + assert.strictEqual(diagnosticRequestFunction.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 0); }); it('should not fall, but options is null', async () => { await assert.doesNotReject(() => - new OutputRequest(diagnosticHttp, diagnosticResponse, undefined).send()); + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, undefined).send()); - assert.strictEqual(diagnosticHttp.request.mock.calls.length, 1); + assert.strictEqual(diagnosticRequestFunction.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 0); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 1); }); it('should fall, cause response is null', async () => { await assert.rejects(() => - new OutputRequest(diagnosticHttp, undefined, testOptions).send(), + new OutputRequest(undefined, diagnosticRequestFunction, testOptions).send(), {name: 'TypeError'}); - assert.strictEqual(diagnosticHttp.request.mock.calls.length, 1); + assert.strictEqual(diagnosticRequestFunction.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 0); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 1); }); @@ -184,10 +179,10 @@ describe('OutputRequest', () => { mock.method(diagnosticResponse, 'copy'); await assert.rejects(() => - new OutputRequest(diagnosticHttp, diagnosticResponse, testOptions).send(), + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, testOptions).send(), {message: 'response copy error'}); - assert.strictEqual(diagnosticHttp.request.mock.calls.length, 1); + assert.strictEqual(diagnosticRequestFunction.mock.calls.length, 1); assert.strictEqual(diagnosticResponse.copy.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 0); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 1); @@ -198,10 +193,10 @@ describe('OutputRequest', () => { mock.method(diagnosticResponse, 'flush'); await assert.rejects(() => - new OutputRequest(diagnosticHttp, diagnosticResponse, testOptions).send(), + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, testOptions).send(), {message: 'response flush error'}); - assert.strictEqual(diagnosticHttp.request.mock.calls.length, 1); + assert.strictEqual(diagnosticRequestFunction.mock.calls.length, 1); assert.strictEqual(diagnosticResponse.copy.mock.calls.length, 1); assert.strictEqual(diagnosticResponse.flush.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 0); @@ -210,47 +205,47 @@ describe('OutputRequest', () => { it('should not fall on GET request', async () => { await assert.doesNotReject(() => - new OutputRequest(diagnosticHttp, diagnosticResponse, {method: 'GET'}).send()); + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, {method: 'GET'}).send()); - assert.strictEqual(diagnosticHttp.request.mock.calls.length, 1); + assert.strictEqual(diagnosticRequestFunction.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 0); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 1); }); it('should not fall on POST request with body', async () => { await assert.doesNotReject(() => - new OutputRequest(diagnosticHttp, diagnosticResponse, {method: 'POST', body: 'test body'}).send()); + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, {method: 'POST', body: 'test body'}).send()); - assert.strictEqual(diagnosticHttp.request.mock.calls.length, 1); + assert.strictEqual(diagnosticRequestFunction.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 1); }); it('should not fall on POST request without body', async () => { await assert.doesNotReject(() => - new OutputRequest(diagnosticHttp, diagnosticResponse, {method: 'POST'}).send()); + new OutputRequest(diagnosticResponse, diagnosticRequestFunction, {method: 'POST'}).send()); - assert.strictEqual(diagnosticHttp.request.mock.calls.length, 1); + assert.strictEqual(diagnosticRequestFunction.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 0); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 1); }); it('should return response with test options', async () => { - const resultResponseInputStream = await new OutputRequest(diagnosticHttp, diagnosticResponse, testOptions).send(); + const resultResponseInputStream = await new OutputRequest(diagnosticResponse, diagnosticRequestFunction, testOptions).send(); assert.deepStrictEqual(resultResponseInputStream.options.options, testOptions); }); it('should send request with url', async () => { - await new OutputRequest(diagnosticHttp, diagnosticResponse, {url: 'test', method: 'GET'}).send(); + await new OutputRequest(diagnosticResponse, diagnosticRequestFunction, {url: 'test', method: 'GET'}).send(); - assert.strictEqual(diagnosticHttp.options.url, 'test'); + assert.strictEqual(diagnosticRequestFunctionOptions.url, 'test'); }); it('should send request without url', async () => { - await new OutputRequest(diagnosticHttp, diagnosticResponse, testOptions).send(); + await new OutputRequest(diagnosticResponse, diagnosticRequestFunction, testOptions).send(); - assert.strictEqual(diagnosticHttp.options.url, undefined); + assert.strictEqual(diagnosticRequestFunctionOptions.url, undefined); }); }); }); \ No newline at end of file diff --git a/src/test/js/server/Server.js b/src/test/js/server/Server.js index d073749..eaae4c3 100644 --- a/src/test/js/server/Server.js +++ b/src/test/js/server/Server.js @@ -1,9 +1,9 @@ /* node:coverage disable */ -const {Server} = require('../../../js/index').server; const {describe, it, mock, beforeEach, afterEach} = require('node:test'); const assert = require('node:assert'); +const {Server} = require('../../../js/index').server; function prepareDiagnostic() { } diff --git a/src/test/js/server/response/JsonOutputResponse.js b/src/test/js/server/response/JsonOutputResponse.js index ba5fcca..8355ec9 100644 --- a/src/test/js/server/response/JsonOutputResponse.js +++ b/src/test/js/server/response/JsonOutputResponse.js @@ -1,9 +1,15 @@ /* node:coverage disable */ -const {JsonOutputResponse} = require('../../../../js/index').server; const {describe, it, mock, beforeEach, afterEach} = require('node:test'); const assert = require('node:assert'); +const {JsonOutputResponse} = require('../../../../js/index').server; + + +const testOptions = { + body: {} +} + const diagnosticOrigin = { copy() { }, @@ -15,9 +21,9 @@ const diagnosticOrigin = { function prepareDiagnostic() { diagnosticOrigin.options = {}; - diagnosticOrigin.copy = (outputStream, options) => { - diagnosticOrigin.options.outputStream = outputStream; + diagnosticOrigin.copy = (options, outputStream) => { diagnosticOrigin.options.options = options; + diagnosticOrigin.options.outputStream = outputStream; return diagnosticOrigin; }; diagnosticOrigin.update = (options) => { @@ -44,8 +50,9 @@ describe('JsonOutputResponse', () => { it('should not call anything', () => { assert.doesNotThrow(() => { new JsonOutputResponse(); - new JsonOutputResponse(null); - new JsonOutputResponse(diagnosticOrigin); + new JsonOutputResponse(null, null); + new JsonOutputResponse(diagnosticOrigin, null); + new JsonOutputResponse(null, testOptions); }); assert.strictEqual(diagnosticOrigin.copy.mock.calls.length, 0); @@ -62,7 +69,7 @@ describe('JsonOutputResponse', () => { const testOutputStream = {content: 'test'}; const testOptions = {content: 'test'}; - new JsonOutputResponse(diagnosticOrigin).copy(testOutputStream, testOptions); + new JsonOutputResponse(diagnosticOrigin).copy(testOptions, testOutputStream); assert.strictEqual(diagnosticOrigin.copy.mock.calls.length, 1); assert.deepStrictEqual(diagnosticOrigin.options.outputStream, testOutputStream); @@ -114,17 +121,24 @@ describe('JsonOutputResponse', () => { afterEach(resetDiagnostic); it('should call flush of origin', () => { - new JsonOutputResponse(diagnosticOrigin).flush(); + new JsonOutputResponse(diagnosticOrigin, testOptions).flush(); assert.strictEqual(diagnosticOrigin.flush.mock.calls.length, 1); }); it('should call update of origin', () => { - new JsonOutputResponse(diagnosticOrigin).flush(); + new JsonOutputResponse(diagnosticOrigin, testOptions).flush(); assert.strictEqual(diagnosticOrigin.update.mock.calls.length, 1); }); + it('should not call update of origin', () => { + new JsonOutputResponse(diagnosticOrigin, {body: null}).flush(); + new JsonOutputResponse(diagnosticOrigin, {body: 'simple string'}).flush(); + + assert.strictEqual(diagnosticOrigin.update.mock.calls.length, 0); + }); + it('should fall when call origin, cause null', () => { assert.throws(() => new JsonOutputResponse().flush(), {name: 'TypeError'}); @@ -138,7 +152,7 @@ describe('JsonOutputResponse', () => { }; mock.method(diagnosticOrigin, 'update'); - assert.throws(() => new JsonOutputResponse(diagnosticOrigin).flush(), {message: 'update error'}); + assert.throws(() => new JsonOutputResponse(diagnosticOrigin, testOptions).flush(), {message: 'update error'}); assert.strictEqual(diagnosticOrigin.flush.mock.calls.length, 0); assert.strictEqual(diagnosticOrigin.update.mock.calls.length, 1); @@ -150,7 +164,7 @@ describe('JsonOutputResponse', () => { }; mock.method(diagnosticOrigin, 'flush'); - assert.throws(() => new JsonOutputResponse(diagnosticOrigin).flush(), {message: 'flush error'}); + assert.throws(() => new JsonOutputResponse(diagnosticOrigin, testOptions).flush(), {message: 'flush error'}); assert.strictEqual(diagnosticOrigin.flush.mock.calls.length, 1); assert.strictEqual(diagnosticOrigin.update.mock.calls.length, 1); @@ -163,14 +177,20 @@ describe('JsonOutputResponse', () => { }; mock.method(diagnosticOrigin, 'flush'); - const resultOutputStream = new JsonOutputResponse(diagnosticOrigin).flush(); + const resultOutputStream = new JsonOutputResponse(diagnosticOrigin, testOptions).flush(); assert.equal(resultOutputStream, testOutputStream); assert.deepStrictEqual(resultOutputStream, testOutputStream); }); - it('should update headers by content-type', () => { - new JsonOutputResponse(diagnosticOrigin).flush(); + it('should update headers with content-type, cause object body', () => { + new JsonOutputResponse(diagnosticOrigin, testOptions).flush(); + + assert.deepStrictEqual(diagnosticOrigin.options.options.headers, {'Content-Type': 'application/json; charset=utf-8'}); + }); + + it('should update headers with content-type, cause string object body', () => { + new JsonOutputResponse(diagnosticOrigin, {body: JSON.stringify({})}).flush(); assert.deepStrictEqual(diagnosticOrigin.options.options.headers, {'Content-Type': 'application/json; charset=utf-8'}); }); diff --git a/src/test/js/server/response/OutputResponse.js b/src/test/js/server/response/OutputResponse.js index 1b3e8ca..e432ace 100644 --- a/src/test/js/server/response/OutputResponse.js +++ b/src/test/js/server/response/OutputResponse.js @@ -51,8 +51,9 @@ describe('OutputResponse', () => { it('should not call anything', () => { assert.doesNotThrow(() => { new OutputResponse(); - new OutputResponse(diagnosticOutputStream, null); - new OutputResponse(diagnosticOutputStream, {}); + new OutputResponse(undefined, diagnosticOutputStream); + new OutputResponse(null, diagnosticOutputStream); + new OutputResponse({}, diagnosticOutputStream); }); assert.strictEqual(diagnosticOutputStream.writeHead.mock.calls.length, 0); @@ -86,7 +87,7 @@ describe('OutputResponse', () => { afterEach(resetDiagnostic); it('should call writeHead, write and end method of outputStream', () => { - new OutputResponse(diagnosticOutputStream, {body: 'test'}) + new OutputResponse({body: 'test'}, diagnosticOutputStream) .flush(); assert.strictEqual(diagnosticOutputStream.writeHead.mock.calls.length, 1); @@ -95,7 +96,7 @@ describe('OutputResponse', () => { }); it('should call only writeHead and end method of outputStream', () => { - new OutputResponse(diagnosticOutputStream, {body: null}) + new OutputResponse({body: null}, diagnosticOutputStream) .flush(); assert.strictEqual(diagnosticOutputStream.writeHead.mock.calls.length, 1); @@ -104,21 +105,21 @@ describe('OutputResponse', () => { }); it('should be equal with input options', () => { - const resultedOutputStream = new OutputResponse(diagnosticOutputStream, testOptions) + const resultedOutputStream = new OutputResponse(testOptions, diagnosticOutputStream) .flush(); assert.deepStrictEqual(resultedOutputStream.options, testOptions); }); it('should be equal with outputStream', () => { - const resultedOutputStream = new OutputResponse(diagnosticOutputStream, testOptions) + const resultedOutputStream = new OutputResponse(testOptions, diagnosticOutputStream) .flush(); assert.equal(resultedOutputStream, diagnosticOutputStream); }); it('should be equal with default options', () => { - const resultedOutputStream = new OutputResponse(diagnosticOutputStream) + const resultedOutputStream = new OutputResponse(null, diagnosticOutputStream) .flush(); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 0); @@ -134,7 +135,7 @@ describe('OutputResponse', () => { }; mock.method(diagnosticOutputStream, 'writeHead'); - assert.throws(() => new OutputResponse(diagnosticOutputStream).flush(), {message: 'writeHead error'}); + assert.throws(() => new OutputResponse(undefined , diagnosticOutputStream).flush(), {message: 'writeHead error'}); assert.strictEqual(diagnosticOutputStream.writeHead.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 0); @@ -146,7 +147,7 @@ describe('OutputResponse', () => { }; mock.method(diagnosticOutputStream, 'write'); - assert.doesNotThrow(() => new OutputResponse(diagnosticOutputStream, {body: null}).flush()); + assert.doesNotThrow(() => new OutputResponse({body: null}, diagnosticOutputStream).flush()); assert.strictEqual(diagnosticOutputStream.writeHead.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.write.mock.calls.length, 0); @@ -158,7 +159,7 @@ describe('OutputResponse', () => { }; mock.method(diagnosticOutputStream, 'write'); - assert.throws(() => new OutputResponse(diagnosticOutputStream, {body: 'now i am falling'}).flush(), + assert.throws(() => new OutputResponse({body: 'now i am falling'}, diagnosticOutputStream).flush(), {message: 'write error'}); assert.strictEqual(diagnosticOutputStream.writeHead.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 1); @@ -171,7 +172,7 @@ describe('OutputResponse', () => { }; mock.method(diagnosticOutputStream, 'end'); - assert.throws(() => new OutputResponse(diagnosticOutputStream).flush(), + assert.throws(() => new OutputResponse(undefined, diagnosticOutputStream).flush(), {message: 'end error'}); assert.strictEqual(diagnosticOutputStream.writeHead.mock.calls.length, 1); assert.strictEqual(diagnosticOutputStream.end.mock.calls.length, 1); @@ -184,25 +185,25 @@ describe('OutputResponse', () => { afterEach(resetDiagnostic); it('should be default', () => { - const resultedOutputStream = new OutputResponse(diagnosticOutputStream) + const resultedOutputStream = new OutputResponse(undefined, diagnosticOutputStream) .update().flush(); assert.deepStrictEqual(resultedOutputStream.options, {statusCode: 200, headers: {}}); }); it('should be updated by statusCode, body and headers', () => { - const resultedOutputStream = new OutputResponse(diagnosticOutputStream) + const resultedOutputStream = new OutputResponse(undefined, diagnosticOutputStream) .update(testOptions).flush(); assert.deepStrictEqual(resultedOutputStream.options, testOptions); }); it('should be equal before and after update', () => { - const beforeUpdateResultedOutputStream = new OutputResponse(diagnosticOutputStream, testOptions) + const beforeUpdateResultedOutputStream = new OutputResponse(testOptions, diagnosticOutputStream) .flush(); assert.deepStrictEqual(beforeUpdateResultedOutputStream.options, testOptions); - const afterUpdateResultedOutputStream = new OutputResponse(diagnosticOutputStream, testOptions) + const afterUpdateResultedOutputStream = new OutputResponse(testOptions, diagnosticOutputStream) .update(testOptions).flush(); assert.deepStrictEqual(afterUpdateResultedOutputStream.options, testOptions); @@ -210,11 +211,11 @@ describe('OutputResponse', () => { }); it('should not be updated by null or undefined option param', () => { - const beforeUpdateResultedOutputStream = new OutputResponse(diagnosticOutputStream, testOptions) + const beforeUpdateResultedOutputStream = new OutputResponse(testOptions, diagnosticOutputStream) .flush(); assert.deepStrictEqual(beforeUpdateResultedOutputStream.options, testOptions); - const afterUpdateResultedOutputStream = new OutputResponse(diagnosticOutputStream, testOptions) + const afterUpdateResultedOutputStream = new OutputResponse(testOptions, diagnosticOutputStream) .update({statusCode: null, headers: undefined}).flush(); assert.deepStrictEqual(afterUpdateResultedOutputStream.options, testOptions); diff --git a/src/test/static/objective-http-test.http b/src/test/static/objective-http-test.http index b34409b..8c841a1 100644 --- a/src/test/static/objective-http-test.http +++ b/src/test/static/objective-http-test.http @@ -32,3 +32,17 @@ Content-Type: application/json; charset=utf-8 "param0": "value0", "param1": "value1" } + + +### Bun GET +GET http://localhost:8080/test?queryKey=sadasdasd&q=1 + + +### Bun POST +POST http://localhost:8080/test?queryKey=sadasdasd&q=1 +Content-Type: application/json; charset=utf-8 + +{ + "param0": "value0", + "param1": "value1" +}