mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 04:48:53 +00:00
feat(server): self-hosted worker (#10085)
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# Snapshot report for `src/__tests__/worker.e2e.ts`
|
||||
|
||||
The actual snapshot is saved in `worker.e2e.ts.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## should proxy image
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{}
|
||||
|
||||
> Snapshot 2
|
||||
|
||||
{
|
||||
code: 'Bad Request',
|
||||
message: 'Missing "url" parameter',
|
||||
name: 'BAD_REQUEST',
|
||||
status: 400,
|
||||
type: 'BAD_REQUEST',
|
||||
}
|
||||
|
||||
> Snapshot 3
|
||||
|
||||
{
|
||||
code: 'Bad Request',
|
||||
message: 'Invalid header',
|
||||
name: 'BAD_REQUEST',
|
||||
status: 400,
|
||||
type: 'BAD_REQUEST',
|
||||
}
|
||||
|
||||
> Snapshot 4
|
||||
|
||||
Buffer @Uint8Array [
|
||||
66616b65 20696d61 6765
|
||||
]
|
||||
|
||||
## should preview link
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{}
|
||||
|
||||
> Snapshot 2
|
||||
|
||||
{
|
||||
code: 'Bad Request',
|
||||
message: 'Invalid URL',
|
||||
name: 'BAD_REQUEST',
|
||||
status: 400,
|
||||
type: 'BAD_REQUEST',
|
||||
}
|
||||
|
||||
> Snapshot 3
|
||||
|
||||
{
|
||||
code: 'Bad Request',
|
||||
message: 'Invalid URL',
|
||||
name: 'BAD_REQUEST',
|
||||
status: 400,
|
||||
type: 'BAD_REQUEST',
|
||||
}
|
||||
|
||||
> Snapshot 4
|
||||
|
||||
{
|
||||
description: 'Test Description',
|
||||
favicons: [
|
||||
'http://localhost:3010/api/worker/image-proxy?url=https%3A%2F%2Fexample.com%2Ffavicon.ico',
|
||||
],
|
||||
images: [
|
||||
'http://localhost:3010/api/worker/image-proxy?url=https%3A%2F%2Fexample.com%2Fimage.png',
|
||||
],
|
||||
title: 'Test Title',
|
||||
url: 'http://example.com/page',
|
||||
videos: [],
|
||||
}
|
||||
Binary file not shown.
@@ -95,7 +95,7 @@ export class TestingApp extends ApplyType<INestApplication>() {
|
||||
}
|
||||
|
||||
request(
|
||||
method: 'get' | 'post' | 'put' | 'delete' | 'patch',
|
||||
method: 'options' | 'get' | 'post' | 'put' | 'delete' | 'patch',
|
||||
path: string
|
||||
): supertest.Test {
|
||||
return supertest(this.getHttpServer())
|
||||
@@ -106,6 +106,10 @@ export class TestingApp extends ApplyType<INestApplication>() {
|
||||
]);
|
||||
}
|
||||
|
||||
OPTIONS(path: string): supertest.Test {
|
||||
return this.request('options', path);
|
||||
}
|
||||
|
||||
GET(path: string): supertest.Test {
|
||||
return this.request('get', path);
|
||||
}
|
||||
|
||||
174
packages/backend/server/src/__tests__/worker.e2e.ts
Normal file
174
packages/backend/server/src/__tests__/worker.e2e.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import type { ExecutionContext, TestFn } from 'ava';
|
||||
import ava from 'ava';
|
||||
import Sinon from 'sinon';
|
||||
import type { Response } from 'supertest';
|
||||
|
||||
import { WorkerModule } from '../plugins/worker';
|
||||
import { createTestingApp, TestingApp } from './utils';
|
||||
|
||||
type TestContext = {
|
||||
app: TestingApp;
|
||||
};
|
||||
|
||||
const test = ava as TestFn<TestContext>;
|
||||
|
||||
test.before(async t => {
|
||||
const app = await createTestingApp({
|
||||
imports: [WorkerModule],
|
||||
});
|
||||
|
||||
t.context.app = app;
|
||||
});
|
||||
|
||||
test.after.always(async t => {
|
||||
await t.context.app.close();
|
||||
});
|
||||
|
||||
const assertAndSnapshotRaw = async (
|
||||
t: ExecutionContext<TestContext>,
|
||||
route: string,
|
||||
message: string,
|
||||
options?: {
|
||||
status?: number;
|
||||
origin?: string;
|
||||
method?: 'GET' | 'OPTIONS' | 'POST';
|
||||
body?: any;
|
||||
checker?: (res: Response) => any;
|
||||
}
|
||||
) => {
|
||||
const {
|
||||
status = 200,
|
||||
origin = 'http://localhost',
|
||||
method = 'GET',
|
||||
checker = () => {},
|
||||
} = options || {};
|
||||
const { app } = t.context;
|
||||
const res = app[method](route)
|
||||
.set('Origin', origin)
|
||||
.send(options?.body)
|
||||
.expect(status)
|
||||
.expect(checker);
|
||||
t.notThrowsAsync(res, message);
|
||||
t.snapshot((await res).body);
|
||||
};
|
||||
|
||||
test('should proxy image', async t => {
|
||||
const assertAndSnapshot = assertAndSnapshotRaw.bind(null, t);
|
||||
|
||||
await assertAndSnapshot(
|
||||
'/api/worker/image-proxy',
|
||||
'should return proper CORS headers on OPTIONS request',
|
||||
{
|
||||
status: 204,
|
||||
method: 'OPTIONS',
|
||||
checker: (res: Response) => {
|
||||
if (!res.headers['access-control-allow-methods']) {
|
||||
throw new Error('Missing CORS headers');
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
{
|
||||
await assertAndSnapshot(
|
||||
'/api/worker/image-proxy',
|
||||
'should return 400 if "url" query parameter is missing',
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
await assertAndSnapshot(
|
||||
'/api/worker/image-proxy?url=http://example.com/image.png',
|
||||
'should return 400 for invalid origin header',
|
||||
{ status: 400, origin: 'http://invalid.com' }
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const fakeBuffer = Buffer.from('fake image');
|
||||
const fakeResponse = {
|
||||
ok: true,
|
||||
headers: {
|
||||
get: (header: string) => {
|
||||
if (header.toLowerCase() === 'content-type') return 'image/png';
|
||||
if (header.toLowerCase() === 'content-disposition') return 'inline';
|
||||
return null;
|
||||
},
|
||||
},
|
||||
arrayBuffer: async () => fakeBuffer,
|
||||
} as any;
|
||||
|
||||
const fetchSpy = Sinon.stub(global, 'fetch').resolves(fakeResponse);
|
||||
|
||||
await assertAndSnapshot(
|
||||
'/api/worker/image-proxy?url=http://example.com/image.png',
|
||||
'should return image buffer'
|
||||
);
|
||||
|
||||
fetchSpy.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('should preview link', async t => {
|
||||
const assertAndSnapshot = assertAndSnapshotRaw.bind(null, t);
|
||||
|
||||
await assertAndSnapshot(
|
||||
'/api/worker/link-preview',
|
||||
'should return proper CORS headers on OPTIONS request',
|
||||
{
|
||||
status: 204,
|
||||
method: 'OPTIONS',
|
||||
checker: (res: Response) => {
|
||||
if (!res.headers['access-control-allow-methods']) {
|
||||
throw new Error('Missing CORS headers');
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await assertAndSnapshot(
|
||||
'/api/worker/link-preview',
|
||||
'should return 400 if request body is invalid',
|
||||
{ status: 400, method: 'POST' }
|
||||
);
|
||||
|
||||
await assertAndSnapshot(
|
||||
'/api/worker/link-preview',
|
||||
'should return 400 if provided URL is from the same origin',
|
||||
{ status: 400, method: 'POST', body: { url: 'http://localhost/somepage' } }
|
||||
);
|
||||
|
||||
{
|
||||
const fakeHTML = new Response(`
|
||||
<html>
|
||||
<head>
|
||||
<meta property="og:title" content="Test Title" />
|
||||
<meta property="og:description" content="Test Description" />
|
||||
<meta property="og:image" content="http://example.com/image.png" />
|
||||
</head>
|
||||
<body>
|
||||
<title>Fallback Title</title>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
|
||||
Object.defineProperty(fakeHTML, 'url', {
|
||||
value: 'http://example.com/page',
|
||||
});
|
||||
|
||||
const fetchSpy = Sinon.stub(global, 'fetch').resolves(fakeHTML);
|
||||
|
||||
await assertAndSnapshot(
|
||||
'/api/worker/link-preview',
|
||||
'should process a valid external URL and return link preview data',
|
||||
{
|
||||
status: 200,
|
||||
method: 'POST',
|
||||
body: { url: 'http://external.com/page' },
|
||||
}
|
||||
);
|
||||
|
||||
fetchSpy.restore();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user