cmorten / superdeno Goto Github PK
View Code? Open in Web Editor NEWSuper-agent driven library for testing Deno HTTP servers.
Home Page: https://cmorten.github.io/superdeno/
License: MIT License
Super-agent driven library for testing Deno HTTP servers.
Home Page: https://cmorten.github.io/superdeno/
License: MIT License
It appears the cookie setting code in Opine has regressed cmorten/opine#117 this means we cannot test that setting multiple cookies works as part of the superdeno test suite.
This issue is to track the resolution of the Opine issue and then re-introduce said commented out tests once it is resolved.
egg.json
now supports a repository field, this should be added.
I noticed that an NPM library was being used to provide node's util
functionality, which brings in a total of 16 NPM packages. (22 vs 6 going by the badges that were just added ๐ )
It seems like this import is easily avoided by leveraging Deno's std library.
After searching the codebase I determined that the util
import from NPM was only being used to polyfill util.inspect()
for error messages. I've prepared a patchset that removes the util
NPM package in favor of Deno.inspect
: main...danopia:use-deno-inspect
Going by the test suite, the only difference I observed was that Deno.inspect uses different quoting rules than the util.inspect from NPM. So I had to adjust some quotes in the test suite. I suppose it's possible that consumers are testing for exact messages too but overall the change doesn't seem very disruptive to me.
I also have a mutually exclusive patchset that uses Deno's /std/node
polyfile for util.inspect
but it doesn't seem to give anything over using Deno.inspect
directly. Here it is anyway: main...danopia:use-std-util
I'm prepared to open a PR with either patchset if this looks agreeable.
Really a question, but with Opine being in maintenance mode, I tried the recommendation of using express
on Deno.
I tried using superdeno
to test routes, but when I run the test, the tests fails and then just hangs the process.
Steps to reproduce:
Run this using deno test
:
// @deno-types="npm:@types/express@^4.17"
import express from "npm:[email protected]";
import { superdeno } from "https://deno.land/x/[email protected]/mod.ts";
const app = express();
app.get("/", (_req, res) => res.json("hello express"));
Deno.test("mod", async () => {
await superdeno(app).get("/").expect("hello express").end();
});
I also tried using npm:supertest
but that doesn't seem to work with npm:express
on Deno either ๐คท .
I guess am not sure what the expectation is (pun unintended) for testing express apps on top of deno. Should I be spinning the app up and down in tests, and then manually calling with fetch
?
Setup:
deno 1.31.1 (release, x86_64-unknown-linux-gnu)
v8 11.0.226.13
typescript 4.9.4
SuperDeno Version: 4.8.0
Besides Opine being in maintenance mode, the hard impetus for trying out express on Deno was because my tests started to fail using superdeno
and opine
together. The tests seem to run, but then an error is thrown after each file is executed by Deno:
error: TypeError: core.runMicrotasks is not a function
core.runMicrotasks();
^
at processTicksAndRejections (https://deno.land/[email protected]/node/_next_tick.ts:62:10)
at https://deno.land/[email protected]/node/process.ts:312:7
With some digging, it seems that opine
is importing theses deps from std
, and those deps are using an api removed in later versions of deno, which is causing an error to be thrown on beforeunload
.
Don't know if this should be an issue on opine
, but it seems opine cannot be used inside of Deno.test
.
Setup:
deno 1.5.4 (bc79d55, release, x86_64-apple-darwin)
v8 8.8.278.2
typescript 4.0.5
2.4.0
Header(s) properties on res
object parameter in .expect()
seem off.
In the latest version (and previous ones!) there appears to be res.header
and res.headers
properties on the res
object provided to the callback of the .expect()
API.
Both appear to have issues with the typings, resulting in needing (res as any)
like workarounds.
I believe the bug lies in the line:
type ExpectChecker = (res: Response) => any;
Which should read:
type ExpectChecker = (res: IResponse) => any;
In order to use the superagent response object, and not the Deno Response object. The whole thing needs a look at though as the real shape of the res
object doesn't quite match the IReponse
object either... so this should be fixed up as well!
Ideally it should conform to, and be typed as, either:
fetch
method)Or... a combination of the above if there is no / sensible overlaps in expected types for named properties.
The Response object has properties:
{
headers: Headers;
ok: boolean;
redirected: boolean;
status: number;
statusText: string;
trailer: Promise<Headers>;
type: ResponseType;
url: string;
body: ReadableStream<Uint8Array> | null;
bodyUsed: boolean;
}
And the superagent response looks like:
{
text: string;
body: string;
header: { [key: string]: string };
type: string;
charset: string;
status: number;
type: number;
info: number;
ok: number;
clientError: number;
serverError: number;
error: number;
accepted: number;
noContent: number;
badRequest: number;
unauthorized: number;
notAcceptable: number;
notFound: number;
forbidden: number;
...
}
HandlerLike
signature doesn't match actual server.Handler
. Missing connInfo
Setup:
The actual server.Handler
takes a second argument called connInfo
that contains information for the http request. See here and quoted:
export type Handler = (
request: Request,
connInfo: ConnInfo,
) => Response | Promise<Response>;
But RequestHandlerLike
defined in this project doesn't match that. And when actually testing the handler, this project calls the handler with return await app(request);
with the connInfo
argument missing.
Hi,
I would like to know if it is possible to run superdeno with just a plain server with serve
from deno http
package, and not with opine or another framework. I can't find any resource.
Thanks a lot.
Setup:
deno 1.12.0 (release, x86_64-unknown-linux-gnu)
v8 9.2.230.14
typescript 4.3.2
At the moment this:
import { superdeno } from 'https://deno.land/x/[email protected]/mod.ts'
import { App } from 'https://deno.land/x/tinyhttp/mod.ts'
import { describe, it, expect, run } from 'https://deno.land/x/[email protected]/mod.ts'
describe('App settings', () => {
it('when disabled should not send anything', async () => {
const app = new App({ settings: { xPoweredBy: false } })
app.use((_req, res) => void res.send('hi'))
const request = superdeno(app.attach)
await request.get('/').expect('X-Powered-By', null)
})
})
run()
results in this:
Error: expected "X-Powered-By" header field
at Test.#assertHeader (https://deno.land/x/[email protected]/src/test.ts:562:14)
at Test.#assertFunction (https://deno.land/x/[email protected]/src/test.ts:617:13)
at Test.#assert (https://deno.land/x/[email protected]/src/test.ts:480:35)
at https://deno.land/x/[email protected]/src/test.ts:455:23
at async close (https://deno.land/x/[email protected]/src/close.ts:48:46)
failures:
App settings > xPoweredBy > when disabled should not send anything
test result: FAILED. 101 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (2657ms)
What's the proper way to test for absence of a header?
When I do something along the lines of:
await superdeno(testApiUrl)
.post('/api/to/endpoint/that/triggers/a/redirect')
.expect((response, error) => {
assertEquals(response.status, 302);
assertEquals(response.headers.includes('location: http://google.com/'), true);
});
This errors out and insists that the final state is 200, not 302.
I've noticed that the response.headers capture those of the final destination page rather than the actual API redirect state. Is there a way to go one step before the final destination URL?
I have also tried
await superdeno(testApiUrl)
.post('/api/to/endpoint/that/triggers/a/redirect')
.expect(302)
.expect((response, error) => {
assertEquals(response.headers.includes('location: http://google.com/'), true);
});
but to no effect.
Thanks heaps (again great library).
Setup:
I'm using oak server. I tried the following code to test a multipart/form-data
endpoint, didn't seem to work.
Both superoak
and superdeno
seemed not to work properly.
await req
.post(url)
.field('form_key', 'form_value')
.attach('form_key2', 'path_to_file', 'filename');
I can only receive an empty body text/plain
request.
I've also tried using superagent
with jspm. Still not working. Maybe its because nodejs package having some bugs on deno by jspm.
Setup:
When running a simple test with [email protected] Deno Test Assertions return
AssertionError: Test case is leaking async ops.
- there are no async's in the example code which is pasted below.
server.ts
import { opine } from 'https://deno.land/x/[email protected]/mod.ts'
export function build() {
const app = opine();
app.get('/', function (req, res) {
res.send('Hello World')
})
return app
}
test.ts
import {superdeno} from 'https://deno.land/x/[email protected]/mod.ts'
import { build } from './server.ts'
const app = build()
Deno.test('hello world', () => {
superdeno(app)
.get('/')
.expect(200)
})
Run
deno test -A --unstable test.ts
Output
running 1 test from file:///workspace/deno-playground/test.ts
test hello world ... FAILED (12ms)
failures:
hello world
AssertionError: Test case is leaking async ops.
Before:
- dispatched: 0
- completed: 0
After:
- dispatched: 2
- completed: 1
Ops:
op_net_accept:
Before:
- dispatched: 0
- completed: 0
After:
- dispatched: 1
- completed: 0
Make sure to await all promises returned from Deno APIs before
finishing test case.
at assert (deno:runtime/js/06_util.js:41:13)
at asyncOpSanitizer (deno:runtime/js/40_testing.js:128:7)
at async resourceSanitizer (deno:runtime/js/40_testing.js:144:7)
at async Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:176:9)
at async runTest (deno:runtime/js/40_testing.js:381:7)
at async Object.runTests (deno:runtime/js/40_testing.js:494:22)
failures:
hello world
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (147ms)
error: Test failed
Setup:
import { superdeno } from 'https://deno.land/x/[email protected]/mod.ts'
import { it, run } from 'https://deno.land/x/[email protected]/mod.ts'
import { App } from 'https://denopkg.com/deno-libs/tinyhttp@new-std-http/mod.ts'
it('simple test', async () => {
const app = new App()
const request = superdeno(app._server!) // or `app.handler`, it doesn't work either way
await request.get('/').expect(404, 'Not Found')
})
run()
Error message:
Error: Request has been terminated
Possible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.
at Test.Request.crossDomainError (https://jspm.dev/npm:[email protected]!cjs:674:13)
at XMLHttpRequestSham.xhr.onreadystatechange (https://jspm.dev/npm:[email protected]!cjs:777:19)
at XMLHttpRequestSham.xhrReceive (https://deno.land/x/[email protected]/src/xhrSham.js:115:29)
at https://deno.land/x/[email protected]/src/xhrSham.js:62:21
at Object.xhr.onreadystatechange (https://deno.land/x/[email protected]/src/xhrSham.js:211:7)
at XMLHttpRequestSham.xhrSend (https://deno.land/x/[email protected]/src/xhrSham.js:335:20)
at async XMLHttpRequestSham.send (https://deno.land/x/[email protected]/src/xhrSham.js:61:7)
Setup:
With new std/http
superdeno doesn't work anymore. superdeno should support new API and deprecate the old one
Setup:
I got the error in the screenshot above on first run (I upgraded from 1.2.1 to 1.5.0). There are actually two (first being a warning re the module implicitly referencing Deno master) and the second being the main blocker:
error: TS2305 [ERROR]: Module '"./_utils"' has no exported member 'normalizeEncoding'.
import { notImplemented, normalizeEncoding } from "./_utils.ts";
Submitting a payload with a get request results in request termination error
Setup:
Please replace this line with a short description of the issue.
await superdeno("http://localhost:8080/api")
.get("/endpoint/")
.send({
"name": "asset X",
"thingType": "truck-asset-type"
})
.expect(200);
results in the error above. It seems like superdeno doesn't support sending payloads with a get request?
Submitting a payload with a GET request is appropriate in this case as there'll be no side effect to the API. The body is essentially a DSL to help filter the response.
Setup:
When using the .attach
method on a request.post
the body.value.read(), appears to be returning undefined for files. I am not sure what the reason for this might be, I have created a test-case showcasing the problem and can create a PR for the same if required. test case file
Setup:
When: testing the following Opine handler:
handler: (req: Request, res: Response, next: NextFunction) => {
console.log(res.headers) // shows: "x-powered-by": "Opine"
res.json({ data: 'ok' })
}
I want the following test:
await superdeno(app)
.get('/')
.expect(200)
.then((response) => {
console.log(response.headers)
})
to return:
{
"content-type": "application/json",
"x-powered-by": "Opine"
}
instead of
{ "content-type": undefined }
.
Full response:
Response {
req: Test {
_query: [],
method: "GET",
url: "http://127.0.0.1:38097/",
header: {},
_header: {},
_callbacks: { "$end": [ [Function] ], "$abort": [ [Function] ] },
app: [Function: app] {
emit: [Function],
... (other methods)
request: ServerRequest { app: [Circular] },
response: Response { app: [Circular] },
cache: {},
engines: {},
settings: {
"x-powered-by": true,
etag: "weak",
"etag fn": [Function: generateETag],
"query parser": "extended",
"query parser fn": [Function: parseExtendedQueryString],
"subdomain offset": 2,
"trust proxy": false,
"trust proxy fn": [Function: trustNone],
view: [Function: View],
views: "<my dir>/views",
"jsonp callback name": "callback",
"view cache": true
},
locals: { settings: [Object] },
mountpath: "/",
_router: [Function: router] {
params: [Object],
_params: [Array],
caseSensitive: false,
mergeParams: undefined,
strict: false,
stack: [Array]
}
},
_maxRedirects: 0,
_endCalled: true,
_callback: [Function],
xhr: XMLHttpRequestSham {
id: "1",
origin: "http://127.0.0.1:38097",
onreadystatechange: [Function],
readyState: 4,
responseText: '{"data":"ok"}',
responseType: "",
response: '{"data":"ok"}',
status: 200,
statusCode: 200,
statusText: "OK",
aborted: false,
options: {
requestHeaders: [Object],
method: "GET",
url: "http://127.0.0.1:38097/",
username: undefined,
password: undefined,
requestBody: null
},
controller: AbortController {},
getAllResponseHeaders: [Function],
getResponseHeader: [Function]
},
_fullfilledPromise: Promise { [Circular] }
},
xhr: XMLHttpRequestSham {
id: "1",
origin: "http://127.0.0.1:38097",
onreadystatechange: [Function],
readyState: 4,
responseText: '{"data":"ok"}',
responseType: "",
response: '{"data":"ok"}',
status: 200,
statusCode: 200,
statusText: "OK",
aborted: false,
options: {
requestHeaders: {},
method: "GET",
url: "http://127.0.0.1:38097/",
username: undefined,
password: undefined,
requestBody: null
},
controller: AbortController {},
getAllResponseHeaders: [Function],
getResponseHeader: [Function]
},
text: '{"data":"ok"}',
statusText: "OK",
statusCode: 200,
status: 200,
statusType: 2,
info: false,
ok: true,
redirect: false,
clientError: false,
serverError: false,
error: false,
created: false,
accepted: false,
noContent: false,
badRequest: false,
unauthorized: false,
notAcceptable: false,
forbidden: false,
notFound: false,
unprocessableEntity: false,
headers: { "content-type": undefined },
header: { "content-type": undefined },
type: "",
links: {},
body: null
}
Setup:
When server response 4xx or 5xx, superdeno's response doesn't have body or text, so there is no way to test the HTTP body part.
import {
Application,
} from "https://deno.land/x/ako/mod.ts";
import { superdeno } from "https://deno.land/x/[email protected]/mod.ts";
import { describe, it } from "https://deno.land/x/[email protected]/test/utils.ts";
describe("ctx.onerror(err)", () => {
it("should respond", async () => {
const app = new Application();
app.use((ctx, next) => {
ctx.body = "something else";
ctx.throw(418, "boom");
});
const res = await superdeno(app.listen())
.head("/")
.expect(418)
.expect("Content-Length", "4")
.expect("Content-Type", "text/plain; charset=utf-8");
console.log(res);
// res.body is null
// res.text is null
});
});
curl:
curl -i 127.0.0.1:5000
HTTP/1.1 418 I'm a teapot
content-length: 4
content-type: text/plain; charset=utf-8
boom%
Setup:
4xx response body is null, even though there is a response body.
I want to test the custom response body, but there is nothing in res body.
By the way, when responding 4xx, superdeno also throws error which is different from supertest, which is annoying.
I get this error when I'm asserting a response body. For some reason it asserts with a raw HTTP response:
res.format(obj) > should send text by default
Error: expected "Hello World" response body, got "HTTP/1.1 20"
at error (https://deno.land/x/[email protected]/src/test.ts:637:15)
at Test.#assertBody (https://deno.land/x/[email protected]/src/test.ts:535:16)
at Test.#assertFunction (https://deno.land/x/[email protected]/src/test.ts:617:13)
at Test.#assert (https://deno.land/x/[email protected]/src/test.ts:480:35)
at https://deno.land/x/[email protected]/src/test.ts:455:23
at async close (https://deno.land/x/[email protected]/src/close.ts:48:46)
failures:
res.format(obj) > should send text by default
test result: FAILED. 121 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (4184ms)
Setup:
deno 1.12.0 (release, x86_64-unknown-linux-gnu)
v8 9.2.230.14
typescript 4.3.2
import { describe, it, run } from 'https://deno.land/x/[email protected]/mod.ts'
import { App, Handler, AppConstructor, Request, Response } from 'https://deno.land/x/tinyhttp/mod.ts'
const runServer = (h: Handler) => {
const app = new App()
app.use(h)
const request = superdeno(app.attach)
return request
}
it('should send text by default', async () => {
const request = runServer((req, res) => {
formatResponse(req, res, () => {})({
text: (req: Request) => req.respond({ body: `Hello World` })
}).end()
})
await request.get('/').expect(200).expect('Hello World')
})
Setup:
Seeing 304 Not Modified
responses with empty (no) body result in an error thrown by SuperDeno.
error: Uncaught Error: Request has been terminated
Possible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.
at Test.Request.crossDomainError (https://dev.jspm.io/npm:[email protected]/lib/client.dew.js:680:15)
at XMLHttpRequestSham.xhr.onreadystatechange (https://dev.jspm.io/npm:[email protected]/lib/client.dew.js:783:21)
at XMLHttpRequestSham.xhrReceive (https://deno.land/x/[email protected]/src/xhrSham.js:105:29)
at XMLHttpRequestSham.send (https://deno.land/x/[email protected]/src/xhrSham.js:64:12)
Adding some logs in the xhrSham we can see that (xhrSham.js:293:34
):
...
window._xhrSham.promises[self.id] = fetch(options.url, {
method: options.method,
headers: options.requestHeaders,
body,
signal: this.controller.signal,
mode: "cors",
});
// Wait on the response, and then read the buffer.
response = await window._xhrSham.promises[self.id];
...
const buf = await response.arrayBuffer();
parsedResponse = decoder.decode(buf); // BOOM!
...
Is throwing:
err: TypeError: Cannot use 'in' operator to search for 'buffer' in null
at TextDecoder.decode (/Users/runner/work/deno/deno/op_crates/web/08_text_encoding.js:445:18)
at XMLHttpRequestSham.xhrSend (https://deno.land/x/[email protected]/src/xhrSham.js:293:34)
at async XMLHttpRequestSham.send (https://deno.land/x/[email protected]/src/xhrSham.js:51:7)
This doesn't occur in Deno 1.3.0
, so assume introduced by one of the changes to Deno core in 1.3.1
, see the release notes: https://github.com/denoland/deno/releases/tag/v1.3.1
08_text_encoding
line ref: https://github.com/denoland/deno/blob/master/op_crates/web/08_text_encoding.js#L445
Reproducing we can see we get the error with:
const decoder = new TextDecoder("utf-8");
const buf = null;
const decoded = decoder.decode(buf as any);
It would seem that response.arrayBuffer()
in Deno 1.3.0 returned ""
for a null body whereas no it returns null
which causes the error in the decoder as it has no null protection.
We can protect against this in SuperDeno by adding our own null
check around the decoder.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.