Skip to content

Commit 29493b4

Browse files
committed
refactoring and more tests
1 parent 1959082 commit 29493b4

File tree

15 files changed

+305
-266
lines changed

15 files changed

+305
-266
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const server = Bun.serve({
2+
fetch() {
3+
return new Response(null, {
4+
status: 200,
5+
statusText: 'OK',
6+
headers: {
7+
'Content-Type': 'image/jpeg',
8+
'Content-Length': '1000',
9+
},
10+
});
11+
},
12+
});
13+
14+
const resp = await fetch(server.url, { method: 'HEAD' });
15+
16+
// should be 1000, but is actually 0
17+
const contentLength = resp.headers.get('Content-Length');
18+
console.log(`Content-length=${contentLength}`);
19+
20+
server.stop(true);

packages/bunshine/src/Context/Context.spec.ts

Lines changed: 1 addition & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -19,89 +19,7 @@ describe('Context', () => {
1919
expect(c.now).toBeNumber();
2020
expect(c.url).toBeInstanceOf(URL);
2121
expect(c.url.pathname).toBe('/home');
22-
});
23-
it('should handle files', async () => {
24-
const request = new Request('http://localhost/home.html');
25-
const app = new HttpRouter();
26-
const c = new Context(request, server, app);
27-
const resp = await c.file(
28-
`${import.meta.dir}/../../testFixtures/home.html`
29-
);
30-
expect(resp).toBeInstanceOf(Response);
31-
expect(resp.headers.get('Accept-Ranges')).toBe('bytes');
32-
const file = await resp.blob();
33-
const text = await file.text();
34-
expect(text).toBe('<h1>Welcome home</h1>\n');
35-
});
36-
it('should handle files with disposition="attachment', async () => {
37-
const request = new Request('http://localhost/home.html');
38-
const app = new HttpRouter();
39-
const c = new Context(request, server, app);
40-
const resp = await c.file(
41-
`${import.meta.dir}/../../testFixtures/home.html`,
42-
{ disposition: 'attachment' }
43-
);
44-
expect(resp).toBeInstanceOf(Response);
45-
expect(resp.headers.get('Content-Disposition')).toBe(
46-
'attachment; filename="home.html"'
47-
);
48-
const file = await resp.blob();
49-
const text = await file.text();
50-
expect(text).toBe('<h1>Welcome home</h1>\n');
51-
});
52-
it('should handle files with range "bytes=0-3"', async () => {
53-
const request = new Request('http://localhost/home.html', {
54-
headers: { Range: 'bytes=0-3' },
55-
});
56-
const app = new HttpRouter();
57-
const c = new Context(request, server, app);
58-
const resp = await c.file(
59-
`${import.meta.dir}/../../testFixtures/home.html`
60-
);
61-
expect(resp).toBeInstanceOf(Response);
62-
const file = await resp.blob();
63-
const text = await file.text();
64-
expect(resp.status).toBe(206);
65-
expect(text).toBe('<h1>');
66-
});
67-
it('should handle files with range "bytes=0-"', async () => {
68-
const request = new Request('http://localhost/home.html', {
69-
headers: { Range: 'bytes=0-' },
70-
});
71-
const app = new HttpRouter();
72-
const c = new Context(request, server, app);
73-
const resp = await c.file(
74-
`${import.meta.dir}/../../testFixtures/home.html`
75-
);
76-
expect(resp).toBeInstanceOf(Response);
77-
const file = await resp.blob();
78-
const text = await file.text();
79-
expect(text).toBe('<h1>Welcome home</h1>\n');
80-
});
81-
it('should handle files with range "bytes=0-999"', async () => {
82-
const request = new Request('http://localhost/home.html', {
83-
headers: { Range: 'bytes=0-999' },
84-
});
85-
const app = new HttpRouter();
86-
const c = new Context(request, server, app);
87-
const resp = await c.file(
88-
`${import.meta.dir}/../../testFixtures/home.html`
89-
);
90-
expect(resp).toBeInstanceOf(Response);
91-
expect(resp.status).toBe(200);
92-
});
93-
it('should handle files with range "bytes=-3"', async () => {
94-
const request = new Request('http://localhost/home.html', {
95-
headers: { Range: 'bytes=-3' },
96-
});
97-
const app = new HttpRouter();
98-
const c = new Context(request, server, app);
99-
const resp = await c.file(
100-
`${import.meta.dir}/../../testFixtures/home.html`
101-
);
102-
expect(resp).toBeInstanceOf(Response);
103-
expect(resp.status).toBe(206);
104-
expect(await resp.text()).toBe('<h1>');
22+
expect(c.locals).toBeTypeOf('object');
10523
});
10624
describe('server', () => {
10725
let c: Context;
@@ -157,15 +75,5 @@ describe('Context', () => {
15775
expect(resp.headers.get('Content-type')).toStartWith('application/json');
15876
expect(resp.headers.get('X-Hello')).toBe('World');
15977
});
160-
// it('should include redirect(url)', () => {
161-
// const resp = redirect('/home');
162-
// expect(resp.headers.get('Location')).toBe('/home');
163-
// expect(resp.status).toBe(302);
164-
// });
165-
// it('should include redirect(url, status)', () => {
166-
// const resp = redirect('/home', 301);
167-
// expect(resp.headers.get('Location')).toBe('/home');
168-
// expect(resp.status).toBe(301);
169-
// });
17078
});
17179
});

packages/bunshine/src/Context/Context.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ export default class Context<
7474
return json.call(this, data, init);
7575
};
7676
/** A shorthand for `new Response(null, { headers: { Location: url }, status: 301 })` */
77-
redirect = (url: string, status = 302) => {
78-
return redirect(url, status);
77+
redirect = (url: string, statusOrInit?: number | ResponseInit) => {
78+
return redirect(url, statusOrInit);
7979
};
8080
/** A shorthand for `new Response(bunFile, fileHeaders)` plus range features */
8181
file = async (
@@ -89,6 +89,6 @@ export default class Context<
8989
};
9090
/** A shorthand for `new Response({ headers: { 'Content-type': 'text/event-stream' } })` */
9191
sse = (setup: SseSetupFunction, init: ResponseInit = {}) => {
92-
return sse(this.request.signal, setup, init);
92+
return sse.call(this, this.request.signal, setup, init);
9393
};
9494
}

packages/bunshine/src/HttpRouter/HttpRouter.spec.ts

Lines changed: 2 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
11
import type { Server } from 'bun';
2-
import { afterEach, beforeEach, describe, expect, it, spyOn } from 'bun:test';
3-
import EventSource from 'eventsource';
2+
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
43
import HttpRouter from './HttpRouter';
54

6-
type SseTestEvent = {
7-
type: string;
8-
data: string;
9-
lastEventId?: string;
10-
origin?: string;
11-
};
12-
135
// @ts-expect-error
146
const server: Server = {};
157

168
describe('HttpRouter', () => {
17-
let port = 7500;
9+
let port = 50100;
1810
describe('handlers', () => {
1911
let app: HttpRouter;
2012
let oldEnv: string | undefined;
@@ -480,135 +472,4 @@ describe('HttpRouter', () => {
480472
expect(await resp.text()).toBe('bar');
481473
});
482474
});
483-
describe('sse', () => {
484-
let app: HttpRouter;
485-
let server: Server;
486-
beforeEach(() => {
487-
app = new HttpRouter();
488-
});
489-
afterEach(() => {
490-
server.stop(true);
491-
});
492-
function sseTest({
493-
port,
494-
payloads,
495-
event,
496-
headers = {},
497-
}: {
498-
port: number;
499-
payloads:
500-
| Array<[string, any, string]>
501-
| Array<[string, any]>
502-
| Array<[string]>;
503-
event: string;
504-
headers?: Record<string, string>;
505-
}): Promise<SseTestEvent[]> {
506-
return new Promise((outerResolve, outerReject) => {
507-
const events: SseTestEvent[] = [];
508-
const readyToSend = new Promise((resolve, reject) => {
509-
app.get('/sse', c => {
510-
return c.sse(
511-
send => {
512-
resolve(() => {
513-
for (const payload of payloads) {
514-
// @ts-expect-error
515-
send(...payload);
516-
}
517-
});
518-
},
519-
{ headers }
520-
);
521-
});
522-
app.onError(c => reject(c.error));
523-
server = app.listen({ port });
524-
}) as Promise<() => void>;
525-
const readyToListen = new Promise((resolve, reject) => {
526-
const stream = new EventSource(`http://localhost:${port}/sse`);
527-
stream.addEventListener('error', evt => {
528-
reject(evt);
529-
stream.close();
530-
});
531-
stream.addEventListener(event, evt => {
532-
events.push(evt);
533-
if (events.length === payloads.length) {
534-
outerResolve(events);
535-
stream.close();
536-
}
537-
});
538-
resolve(port);
539-
}) as Promise<number>;
540-
Promise.all([readyToSend, readyToListen])
541-
.then(([doSend]) => doSend())
542-
.catch(outerReject);
543-
});
544-
}
545-
it('should handle unnamed data', async () => {
546-
const events = await sseTest({
547-
event: 'message',
548-
port: port++,
549-
payloads: [['Hello'], ['World']],
550-
});
551-
expect(events.length).toBe(2);
552-
expect(events[0].data).toBe('Hello');
553-
expect(events[1].data).toBe('World');
554-
});
555-
it('should send last event id and origin', async () => {
556-
const events = await sseTest({
557-
event: 'myEvent',
558-
port: port++,
559-
payloads: [
560-
['myEvent', 'hi1', 'id1'],
561-
['myEvent', 'hi2', 'id2'],
562-
],
563-
});
564-
expect(events.length).toBe(2);
565-
expect(events[0].data).toBe('hi1');
566-
expect(events[1].data).toBe('hi2');
567-
expect(events[0].lastEventId).toBe('id1');
568-
expect(events[1].lastEventId).toBe('id2');
569-
expect(events[0].origin).toBe(`http://localhost:${port - 1}`);
570-
expect(events[1].origin).toBe(`http://localhost:${port - 1}`);
571-
});
572-
it('should JSON encode data if needed', async () => {
573-
const events = await sseTest({
574-
event: 'myEvent',
575-
port: port++,
576-
payloads: [['myEvent', { name: 'Bob' }]],
577-
});
578-
expect(events.length).toBe(1);
579-
expect(events[0].data).toBe('{"name":"Bob"}');
580-
});
581-
it('should warn when overriding some headers', async () => {
582-
spyOn(console, 'warn').mockImplementation(() => {});
583-
await sseTest({
584-
event: 'myEvent',
585-
port: port++,
586-
payloads: [['myEvent', { name: 'Bob' }]],
587-
headers: {
588-
'Content-Type': 'text/plain',
589-
'Cache-Control': 'foo',
590-
Connection: 'whatever',
591-
},
592-
});
593-
expect(console.warn).toHaveBeenCalledTimes(3);
594-
// @ts-expect-error
595-
console.warn.mockRestore();
596-
});
597-
it('should not warn if those headers are correct', async () => {
598-
spyOn(console, 'warn').mockImplementation(() => {});
599-
await sseTest({
600-
event: 'myEvent',
601-
port: port++,
602-
payloads: [['myEvent', { name: 'Bob' }]],
603-
headers: {
604-
'Content-Type': 'text/event-stream',
605-
'Cache-Control': 'no-cache',
606-
Connection: 'keep-alive',
607-
},
608-
});
609-
expect(console.warn).toHaveBeenCalledTimes(0);
610-
// @ts-expect-error
611-
console.warn.mockRestore();
612-
});
613-
});
614475
});

packages/bunshine/src/HttpRouter/HttpRouter.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -226,17 +226,12 @@ export default class HttpRouter {
226226
context.params = match[1];
227227

228228
try {
229-
let result = handler(context, next);
229+
let result = await handler(context, next);
230230
if (result instanceof Response) {
231231
return result;
232+
} else {
233+
return next();
232234
}
233-
if (typeof result?.then === 'function') {
234-
result = await result;
235-
if (result instanceof Response) {
236-
return result;
237-
}
238-
}
239-
return next();
240235
} catch (e) {
241236
return errorHandler(e as Error);
242237
}

packages/bunshine/src/middleware/compression/compression.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ export function compression(
3535
const resolvedOptions = { ...compressionDefaults, ...options };
3636
return async (context, next) => {
3737
const resp = await next();
38-
if (!isCompressibleMime(resp.headers.get('Content-Type'))) {
38+
if (
39+
context.request.method === 'HEAD' ||
40+
!isCompressibleMime(resp.headers.get('Content-Type'))
41+
) {
3942
return resp;
4043
}
4144
const accept = context.request.headers.get('Accept-Encoding') ?? '';

packages/bunshine/src/middleware/serveFiles/serveFiles.spec.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ describe('serveFiles middleware', () => {
2222
return c.text('Error', { status: 500 });
2323
});
2424
});
25-
afterEach(() => {
26-
server.stop(true);
27-
});
25+
afterEach(() => server.stop(true));
2826
describe('files', () => {
2927
it('should serve file', async () => {
3028
app.get('/files/*', serveFiles(fixturesPath));
@@ -53,7 +51,7 @@ describe('serveFiles middleware', () => {
5351
const text = await resp.text();
5452
expect(text).toBe('');
5553
expect(resp.headers.get('content-length')).toBe('0');
56-
expect(resp.status).toBe(204);
54+
expect(resp.status).toBe(200);
5755
});
5856
it('should 404 if file does not exist', async () => {
5957
app.get('/files/*', serveFiles(fixturesPath));

0 commit comments

Comments
 (0)