test(backend): Add tests for web resources (#10341)
This commit is contained in:
parent
3757d3ab88
commit
8571c692ba
2 changed files with 474 additions and 135 deletions
|
@ -1,7 +1,8 @@
|
||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { startServer, signup, post, api, simpleGet } from '../utils.js';
|
import { startServer, channel, clip, cookie, galleryPost, signup, page, play, post, simpleGet, uploadFile } from '../utils.js';
|
||||||
|
import type { SimpleGetResponse } from '../utils.js';
|
||||||
import type { INestApplicationContext } from '@nestjs/common';
|
import type { INestApplicationContext } from '@nestjs/common';
|
||||||
|
|
||||||
// Request Accept
|
// Request Accept
|
||||||
|
@ -15,189 +16,446 @@ const AP = 'application/activity+json; charset=utf-8';
|
||||||
const HTML = 'text/html; charset=utf-8';
|
const HTML = 'text/html; charset=utf-8';
|
||||||
const JSON_UTF8 = 'application/json; charset=utf-8';
|
const JSON_UTF8 = 'application/json; charset=utf-8';
|
||||||
|
|
||||||
describe('Fetch resource', () => {
|
describe('Webリソース', () => {
|
||||||
let app: INestApplicationContext;
|
let app: INestApplicationContext;
|
||||||
|
|
||||||
let alice: any;
|
let alice: any;
|
||||||
|
let aliceUploadedFile: any;
|
||||||
let alicesPost: any;
|
let alicesPost: any;
|
||||||
|
let alicePage: any;
|
||||||
|
let alicePlay: any;
|
||||||
|
let aliceClip: any;
|
||||||
|
let aliceGalleryPost: any;
|
||||||
|
let aliceChannel: any;
|
||||||
|
|
||||||
|
type Request = {
|
||||||
|
path: string,
|
||||||
|
accept?: string,
|
||||||
|
cookie?: string,
|
||||||
|
};
|
||||||
|
const ok = async (param: Request & {
|
||||||
|
type?: string,
|
||||||
|
}):Promise<SimpleGetResponse> => {
|
||||||
|
const { path, accept, cookie, type } = param;
|
||||||
|
const res = await simpleGet(path, accept, cookie);
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
assert.strictEqual(res.type, type ?? HTML);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
const notOk = async (param: Request & {
|
||||||
|
status?: number,
|
||||||
|
code?: string,
|
||||||
|
}): Promise<SimpleGetResponse> => {
|
||||||
|
const { path, accept, cookie, status, code } = param;
|
||||||
|
const res = await simpleGet(path, accept, cookie);
|
||||||
|
assert.notStrictEqual(res.status, 200);
|
||||||
|
if (status != null) {
|
||||||
|
assert.strictEqual(res.status, status);
|
||||||
|
}
|
||||||
|
if (code != null) {
|
||||||
|
assert.strictEqual(res.body.error.code, code);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
const notFound = async (param: Request): Promise<SimpleGetResponse> => {
|
||||||
|
return await notOk({
|
||||||
|
...param,
|
||||||
|
status: 404,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const metaTag = (res: SimpleGetResponse, key: string, superkey = 'name'): string => {
|
||||||
|
return res.body.window.document.querySelector('meta[' + superkey + '="' + key + '"]')?.content;
|
||||||
|
};
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
app = await startServer();
|
app = await startServer();
|
||||||
alice = await signup({ username: 'alice' });
|
alice = await signup({ username: 'alice' });
|
||||||
|
aliceUploadedFile = await uploadFile(alice);
|
||||||
alicesPost = await post(alice, {
|
alicesPost = await post(alice, {
|
||||||
text: 'test',
|
text: 'test',
|
||||||
});
|
});
|
||||||
|
alicePage = await page(alice, {});
|
||||||
|
alicePlay = await play(alice, {});
|
||||||
|
aliceClip = await clip(alice, {});
|
||||||
|
aliceGalleryPost = await galleryPost(alice, {
|
||||||
|
fileIds: [aliceUploadedFile.body.id],
|
||||||
|
});
|
||||||
|
aliceChannel = await channel(alice, {});
|
||||||
}, 1000 * 60 * 2);
|
}, 1000 * 60 * 2);
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await app.close();
|
await app.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Common', () => {
|
describe.each([
|
||||||
test('meta', async () => {
|
{ path: '/', type: HTML },
|
||||||
const res = await api('/meta', {
|
{ path: '/docs/ja-JP/about', type: HTML }, // "指定されたURLに該当するページはありませんでした。"
|
||||||
});
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET root', async () => {
|
|
||||||
const res = await simpleGet('/');
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, HTML);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET docs', async () => {
|
|
||||||
const res = await simpleGet('/docs/ja-JP/about');
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, HTML);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET api-doc', async () => {
|
|
||||||
const res = await simpleGet('/api-doc');
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
// fastify-static gives charset=UTF-8 instead of utf-8 and that's okay
|
// fastify-static gives charset=UTF-8 instead of utf-8 and that's okay
|
||||||
assert.strictEqual(res.type?.toLowerCase(), HTML);
|
{ path: '/api-doc', type: 'text/html; charset=UTF-8' },
|
||||||
|
{ path: '/api.json', type: JSON_UTF8 },
|
||||||
|
{ path: '/api-console', type: HTML },
|
||||||
|
{ path: '/_info_card_', type: HTML },
|
||||||
|
{ path: '/bios', type: HTML },
|
||||||
|
{ path: '/cli', type: HTML },
|
||||||
|
{ path: '/flush', type: HTML },
|
||||||
|
{ path: '/robots.txt', type: 'text/plain; charset=UTF-8' },
|
||||||
|
{ path: '/favicon.ico', type: 'image/vnd.microsoft.icon' },
|
||||||
|
{ path: '/opensearch.xml', type: 'application/opensearchdescription+xml' },
|
||||||
|
{ path: '/apple-touch-icon.png', type: 'image/png' },
|
||||||
|
{ path: '/twemoji/2764.svg', type: 'image/svg+xml' },
|
||||||
|
{ path: '/twemoji/2764-fe0f-200d-1f525.svg', type: 'image/svg+xml' },
|
||||||
|
{ path: '/twemoji-badge/2764.png', type: 'image/png' },
|
||||||
|
{ path: '/twemoji-badge/2764-fe0f-200d-1f525.png', type: 'image/png' },
|
||||||
|
{ path: '/fluent-emoji/2764.png', type: 'image/png' },
|
||||||
|
{ path: '/fluent-emoji/2764-fe0f-200d-1f525.png', type: 'image/png' },
|
||||||
|
])('$path', (p) => {
|
||||||
|
test('がGETできる。', async () => await ok({ ...p }));
|
||||||
|
|
||||||
|
// 注意: Webページが200で取得できても、実際のHTMLが正しく表示できるとは限らない
|
||||||
|
// 例えば、 /@xxx/pages/yyy に存在しないIDを渡した場合、HTTPレスポンスではエラーを区別できない
|
||||||
|
// こういったアサーションはフロントエンドE2EやAPI Endpointのテストで担保する。
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET api.json', async () => {
|
describe.each([
|
||||||
const res = await simpleGet('/api.json');
|
{ path: '/twemoji/2764.png' },
|
||||||
assert.strictEqual(res.status, 200);
|
{ path: '/twemoji/2764-fe0f-200d-1f525.png' },
|
||||||
assert.strictEqual(res.type, JSON_UTF8);
|
{ path: '/twemoji-badge/2764.svg' },
|
||||||
|
{ path: '/twemoji-badge/2764-fe0f-200d-1f525.svg' },
|
||||||
|
{ path: '/fluent-emoji/2764.svg' },
|
||||||
|
{ path: '/fluent-emoji/2764-fe0f-200d-1f525.svg' },
|
||||||
|
])('$path', ({ path }) => {
|
||||||
|
test('はGETできない。', async () => await notFound({ path }));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET api/foo (存在しない)', async () => {
|
describe.each([
|
||||||
const res = await simpleGet('/api/foo');
|
{ ext: 'rss', type: 'application/rss+xml; charset=utf-8' },
|
||||||
assert.strictEqual(res.status, 404);
|
{ ext: 'atom', type: 'application/atom+xml; charset=utf-8' },
|
||||||
assert.strictEqual(res.body.error.code, 'UNKNOWN_API_ENDPOINT');
|
{ ext: 'json', type: 'application/json; charset=utf-8' },
|
||||||
|
])('/@:username.$ext', ({ ext, type }) => {
|
||||||
|
const path = (username: string): string => `/@${username}.${ext}`;
|
||||||
|
|
||||||
|
test('がGETできる。', async () => await ok({
|
||||||
|
path: path(alice.username),
|
||||||
|
type,
|
||||||
|
}));
|
||||||
|
|
||||||
|
test('は存在しないユーザーはGETできない。', async () => await notOk({
|
||||||
|
path: path('nonexisting'),
|
||||||
|
status: 404,
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET api-console (client page)', async () => {
|
describe.each([{ path: '/api/foo' }])('$path', ({ path }) => {
|
||||||
const res = await simpleGet('/api-console');
|
test('はGETできない。', async () => await notOk({
|
||||||
assert.strictEqual(res.status, 200);
|
path,
|
||||||
assert.strictEqual(res.type, HTML);
|
status: 404,
|
||||||
|
code: 'UNKNOWN_API_ENDPOINT',
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET favicon.ico', async () => {
|
describe.each([{ path: '/queue' }])('$path', ({ path }) => {
|
||||||
const res = await simpleGet('/favicon.ico');
|
test('はadminでなければGETできない。', async () => await notOk({
|
||||||
assert.strictEqual(res.status, 200);
|
path,
|
||||||
assert.strictEqual(res.type, 'image/vnd.microsoft.icon');
|
status: 500, // FIXME? 403ではない。
|
||||||
|
}));
|
||||||
|
|
||||||
|
test('はadminならGETできる。', async () => await ok({
|
||||||
|
path,
|
||||||
|
cookie: cookie(alice),
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET apple-touch-icon.png', async () => {
|
describe.each([{ path: '/streaming' }])('$path', ({ path }) => {
|
||||||
const res = await simpleGet('/apple-touch-icon.png');
|
test('はGETできない。', async () => await notOk({
|
||||||
assert.strictEqual(res.status, 200);
|
path,
|
||||||
assert.strictEqual(res.type, 'image/png');
|
status: 503,
|
||||||
});
|
}));
|
||||||
|
|
||||||
test('GET twemoji svg', async () => {
|
|
||||||
const res = await simpleGet('/twemoji/2764.svg');
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, 'image/svg+xml');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET twemoji svg with hyphen', async () => {
|
|
||||||
const res = await simpleGet('/twemoji/2764-fe0f-200d-1f525.svg');
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, 'image/svg+xml');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('/@:username', () => {
|
describe('/@:username', () => {
|
||||||
test('Only AP => AP', async () => {
|
const path = (username: string): string => `/@${username}`;
|
||||||
const res = await simpleGet(`/@${alice.username}`, ONLY_AP);
|
|
||||||
assert.strictEqual(res.status, 200);
|
describe.each([
|
||||||
assert.strictEqual(res.type, AP);
|
{ accept: PREFER_HTML },
|
||||||
|
{ accept: UNSPECIFIED },
|
||||||
|
])('(Acceptヘッダ: $accept)', ({ accept }) => {
|
||||||
|
test('はHTMLとしてGETできる。', async () => {
|
||||||
|
const res = await ok({
|
||||||
|
path: path(alice.username),
|
||||||
|
accept,
|
||||||
|
type: HTML,
|
||||||
|
});
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
|
||||||
|
|
||||||
|
// TODO ogタグの検証
|
||||||
|
// TODO profile.noCrawleの検証
|
||||||
|
// TODO twitter:creatorの検証
|
||||||
|
// TODO <link rel="me" ...>の検証
|
||||||
|
});
|
||||||
|
test('はHTMLとしてGETできる。(存在しないIDでも。)', async () => await ok({
|
||||||
|
path: path('xxxxxxxxxx'),
|
||||||
|
type: HTML,
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Prefer AP => AP', async () => {
|
describe.each([
|
||||||
const res = await simpleGet(`/@${alice.username}`, PREFER_AP);
|
{ accept: ONLY_AP },
|
||||||
assert.strictEqual(res.status, 200);
|
{ accept: PREFER_AP },
|
||||||
assert.strictEqual(res.type, AP);
|
])('(Acceptヘッダ: $accept)', ({ accept }) => {
|
||||||
|
test('はActivityPubとしてGETできる。', async () => {
|
||||||
|
const res = await ok({
|
||||||
|
path: path(alice.username),
|
||||||
|
accept,
|
||||||
|
type: AP,
|
||||||
|
});
|
||||||
|
assert.strictEqual(res.body.type, 'Person');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Prefer HTML => HTML', async () => {
|
test('は存在しないIDのときActivityPubとしてGETできない。', async () => await notFound({
|
||||||
const res = await simpleGet(`/@${alice.username}`, PREFER_HTML);
|
path: path('xxxxxxxxxx'),
|
||||||
assert.strictEqual(res.status, 200);
|
accept,
|
||||||
assert.strictEqual(res.type, HTML);
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unspecified => HTML', async () => {
|
describe.each([
|
||||||
const res = await simpleGet(`/@${alice.username}`, UNSPECIFIED);
|
// 実際のハンドルはフロントエンド(index.vue)で行われる
|
||||||
assert.strictEqual(res.status, 200);
|
{ sub: 'home' },
|
||||||
assert.strictEqual(res.type, HTML);
|
{ sub: 'notes' },
|
||||||
|
{ sub: 'activity' },
|
||||||
|
{ sub: 'achievements' },
|
||||||
|
{ sub: 'reactions' },
|
||||||
|
{ sub: 'clips' },
|
||||||
|
{ sub: 'pages' },
|
||||||
|
{ sub: 'gallery' },
|
||||||
|
])('/@:username/$sub', ({ sub }) => {
|
||||||
|
const path = (username: string): string => `/@${username}/${sub}`;
|
||||||
|
|
||||||
|
test('はHTMLとしてGETできる。', async () => {
|
||||||
|
const res = await ok({
|
||||||
|
path: path(alice.username),
|
||||||
});
|
});
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/@:user/pages/:page', () => {
|
||||||
|
const path = (username: string, pagename: string): string => `/@${username}/pages/${pagename}`;
|
||||||
|
|
||||||
|
test('はHTMLとしてGETできる。', async () => {
|
||||||
|
const res = await ok({
|
||||||
|
path: path(alice.username, alicePage.name),
|
||||||
|
});
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:page-id'), alicePage.id);
|
||||||
|
|
||||||
|
// TODO ogタグの検証
|
||||||
|
// TODO profile.noCrawleの検証
|
||||||
|
// TODO twitter:creatorの検証
|
||||||
|
});
|
||||||
|
|
||||||
|
test('はGETできる。(存在しないIDでも。)', async () => await ok({
|
||||||
|
path: path(alice.username, 'xxxxxxxxxx'),
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('/users/:id', () => {
|
describe('/users/:id', () => {
|
||||||
test('Only AP => AP', async () => {
|
const path = (id: string): string => `/users/${id}`;
|
||||||
const res = await simpleGet(`/users/${alice.id}`, ONLY_AP);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, AP);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Prefer AP => AP', async () => {
|
describe.each([
|
||||||
const res = await simpleGet(`/users/${alice.id}`, PREFER_AP);
|
{ accept: PREFER_HTML },
|
||||||
assert.strictEqual(res.status, 200);
|
{ accept: UNSPECIFIED },
|
||||||
assert.strictEqual(res.type, AP);
|
])('(Acceptヘッダ: $accept)', ({ accept }) => {
|
||||||
});
|
test('は/@:usernameにリダイレクトする', async () => {
|
||||||
|
const res = await simpleGet(path(alice.id), accept);
|
||||||
test('Prefer HTML => Redirect to /@:username', async () => {
|
|
||||||
const res = await simpleGet(`/users/${alice.id}`, PREFER_HTML);
|
|
||||||
assert.strictEqual(res.status, 302);
|
assert.strictEqual(res.status, 302);
|
||||||
assert.strictEqual(res.location, `/@${alice.username}`);
|
assert.strictEqual(res.location, `/@${alice.username}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Undecided => HTML', async () => {
|
test('は存在しないユーザーはGETできない。', async () => await notFound({
|
||||||
const res = await simpleGet(`/users/${alice.id}`, UNSPECIFIED);
|
path: path('xxxxxxxx'),
|
||||||
assert.strictEqual(res.status, 302);
|
}));
|
||||||
assert.strictEqual(res.location, `/@${alice.username}`);
|
});
|
||||||
|
|
||||||
|
describe.each([
|
||||||
|
{ accept: ONLY_AP },
|
||||||
|
{ accept: PREFER_AP },
|
||||||
|
])('(Acceptヘッダ: $accept)', ({ accept }) => {
|
||||||
|
test('はActivityPubとしてGETできる。', async () => {
|
||||||
|
const res = await ok({
|
||||||
|
path: path(alice.id),
|
||||||
|
accept,
|
||||||
|
type: AP,
|
||||||
|
});
|
||||||
|
assert.strictEqual(res.body.type, 'Person');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('は存在しないIDのときActivityPubとしてGETできない。', async () => await notOk({
|
||||||
|
path: path('xxxxxxxx'),
|
||||||
|
accept,
|
||||||
|
status: 404,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/users/inbox', () => {
|
||||||
|
test('がGETできる。(POST専用だけど4xx/5xxにならずHTMLが返ってくる)', async () => await ok({
|
||||||
|
path: '/inbox',
|
||||||
|
}));
|
||||||
|
|
||||||
|
// test.todo('POSTできる?');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/users/:id/inbox', () => {
|
||||||
|
const path = (id: string): string => `/users/${id}/inbox`;
|
||||||
|
|
||||||
|
test('がGETできる。(POST専用だけど4xx/5xxにならずHTMLが返ってくる)', async () => await ok({
|
||||||
|
path: path(alice.id),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// test.todo('POSTできる?');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/users/:id/outbox', () => {
|
||||||
|
const path = (id: string): string => `/users/${id}/outbox`;
|
||||||
|
|
||||||
|
test('がGETできる。', async () => {
|
||||||
|
const res = await ok({
|
||||||
|
path: path(alice.id),
|
||||||
|
type: AP,
|
||||||
|
});
|
||||||
|
assert.strictEqual(res.body.type, 'OrderedCollection');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('/notes/:id', () => {
|
describe('/notes/:id', () => {
|
||||||
test('Only AP => AP', async () => {
|
const path = (noteId: string): string => `/notes/${noteId}`;
|
||||||
const res = await simpleGet(`/notes/${alicesPost.id}`, ONLY_AP);
|
|
||||||
assert.strictEqual(res.status, 200);
|
describe.each([
|
||||||
assert.strictEqual(res.type, AP);
|
{ accept: PREFER_HTML },
|
||||||
|
{ accept: UNSPECIFIED },
|
||||||
|
])('(Acceptヘッダ: $accept)', ({ accept }) => {
|
||||||
|
test('はHTMLとしてGETできる。', async () => {
|
||||||
|
const res = await ok({
|
||||||
|
path: path(alicesPost.id),
|
||||||
|
accept,
|
||||||
|
type: HTML,
|
||||||
|
});
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:note-id'), alicesPost.id);
|
||||||
|
|
||||||
|
// TODO ogタグの検証
|
||||||
|
// TODO profile.noCrawleの検証
|
||||||
|
// TODO twitter:creatorの検証
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Prefer AP => AP', async () => {
|
test('はHTMLとしてGETできる。(存在しないIDでも。)', async () => await ok({
|
||||||
const res = await simpleGet(`/notes/${alicesPost.id}`, PREFER_AP);
|
path: path('xxxxxxxxxx'),
|
||||||
assert.strictEqual(res.status, 200);
|
}));
|
||||||
assert.strictEqual(res.type, AP);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Prefer HTML => HTML', async () => {
|
describe.each([
|
||||||
const res = await simpleGet(`/notes/${alicesPost.id}`, PREFER_HTML);
|
{ accept: ONLY_AP },
|
||||||
assert.strictEqual(res.status, 200);
|
{ accept: PREFER_AP },
|
||||||
assert.strictEqual(res.type, HTML);
|
])('(Acceptヘッダ: $accept)', ({ accept }) => {
|
||||||
|
test('はActivityPubとしてGETできる。', async () => {
|
||||||
|
const res = await ok({
|
||||||
|
path: path(alicesPost.id),
|
||||||
|
accept,
|
||||||
|
type: AP,
|
||||||
|
});
|
||||||
|
assert.strictEqual(res.body.type, 'Note');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unspecified => HTML', async () => {
|
test('は存在しないIDのときActivityPubとしてGETできない。', async () => await notFound({
|
||||||
const res = await simpleGet(`/notes/${alicesPost.id}`, UNSPECIFIED);
|
path: path('xxxxxxxxxx'),
|
||||||
assert.strictEqual(res.status, 200);
|
accept,
|
||||||
assert.strictEqual(res.type, HTML);
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Feeds', () => {
|
describe('/play/:id', () => {
|
||||||
test('RSS', async () => {
|
const path = (playid: string): string => `/play/${playid}`;
|
||||||
const res = await simpleGet(`/@${alice.username}.rss`, UNSPECIFIED);
|
|
||||||
assert.strictEqual(res.status, 200);
|
test('がGETできる。', async () => {
|
||||||
assert.strictEqual(res.type, 'application/rss+xml; charset=utf-8');
|
const res = await ok({
|
||||||
|
path: path(alicePlay.id),
|
||||||
|
});
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:flash-id'), alicePlay.id);
|
||||||
|
|
||||||
|
// TODO ogタグの検証
|
||||||
|
// TODO profile.noCrawleの検証
|
||||||
|
// TODO twitter:creatorの検証
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ATOM', async () => {
|
test('がGETできる。(存在しないIDでも。)', async () => await ok({
|
||||||
const res = await simpleGet(`/@${alice.username}.atom`, UNSPECIFIED);
|
path: path('xxxxxxxxxx'),
|
||||||
assert.strictEqual(res.status, 200);
|
}));
|
||||||
assert.strictEqual(res.type, 'application/atom+xml; charset=utf-8');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('JSON', async () => {
|
describe('/clips/:clip', () => {
|
||||||
const res = await simpleGet(`/@${alice.username}.json`, UNSPECIFIED);
|
const path = (clip: string): string => `/clips/${clip}`;
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, 'application/json; charset=utf-8');
|
test('がGETできる。', async () => {
|
||||||
|
const res = await ok({
|
||||||
|
path: path(aliceClip.id),
|
||||||
});
|
});
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:clip-id'), aliceClip.id);
|
||||||
|
|
||||||
|
// TODO ogタグの検証
|
||||||
|
// TODO profile.noCrawleの検証
|
||||||
|
});
|
||||||
|
|
||||||
|
test('がGETできる。(存在しないIDでも。)', async () => await ok({
|
||||||
|
path: path('xxxxxxxxxx'),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/gallery/:post', () => {
|
||||||
|
const path = (post: string): string => `/gallery/${post}`;
|
||||||
|
|
||||||
|
test('がGETできる。', async () => {
|
||||||
|
const res = await ok({
|
||||||
|
path: path(aliceGalleryPost.id),
|
||||||
|
});
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
|
||||||
|
assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
|
||||||
|
|
||||||
|
// FIXME: misskey:gallery-post-idみたいなmetaタグの設定がない
|
||||||
|
// TODO profile.noCrawleの検証
|
||||||
|
// TODO twitter:creatorの検証
|
||||||
|
});
|
||||||
|
|
||||||
|
test('がGETできる。(存在しないIDでも。)', async () => await ok({
|
||||||
|
path: path('xxxxxxxxxx'),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/channels/:channel', () => {
|
||||||
|
const path = (channel: string): string => `/channels/${channel}`;
|
||||||
|
|
||||||
|
test('はGETできる。', async () => {
|
||||||
|
const res = await ok({
|
||||||
|
path: path(aliceChannel.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
// FIXME: misskey関連のmetaタグの設定がない
|
||||||
|
// TODO ogタグの検証
|
||||||
|
});
|
||||||
|
|
||||||
|
test('がGETできる。(存在しないIDでも。)', async () => await ok({
|
||||||
|
path: path('xxxxxxxxxx'),
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { isAbsolute, basename } from 'node:path';
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws';
|
||||||
import fetch, { Blob, File, RequestInit } from 'node-fetch';
|
import fetch, { Blob, File, RequestInit } from 'node-fetch';
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
|
import { JSDOM } from 'jsdom';
|
||||||
import { entities } from '../src/postgres.js';
|
import { entities } from '../src/postgres.js';
|
||||||
import { loadConfig } from '../src/config.js';
|
import { loadConfig } from '../src/config.js';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
@ -12,6 +13,10 @@ export { server as startServer } from '@/boot/common.js';
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
export const port = config.port;
|
export const port = config.port;
|
||||||
|
|
||||||
|
export const cookie = (me: any): string => {
|
||||||
|
return `token=${me.token};`;
|
||||||
|
};
|
||||||
|
|
||||||
export const api = async (endpoint: string, params: any, me?: any) => {
|
export const api = async (endpoint: string, params: any, me?: any) => {
|
||||||
const normalized = endpoint.replace(/^\//, '');
|
const normalized = endpoint.replace(/^\//, '');
|
||||||
return await request(`api/${normalized}`, params, me);
|
return await request(`api/${normalized}`, params, me);
|
||||||
|
@ -71,6 +76,71 @@ export const react = async (user: any, note: any, reaction: string): Promise<any
|
||||||
}, user);
|
}, user);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const page = async (user: any, page: any = {}): Promise<any> => {
|
||||||
|
const res = await api('pages/create', {
|
||||||
|
alignCenter: false,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
id: '2be9a64b-5ada-43a3-85f3-ec3429551ded',
|
||||||
|
text: 'Hello World!',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
eyeCatchingImageId: null,
|
||||||
|
font: 'sans-serif',
|
||||||
|
hideTitleWhenPinned: false,
|
||||||
|
name: '1678594845072',
|
||||||
|
script: '',
|
||||||
|
summary: null,
|
||||||
|
title: '',
|
||||||
|
variables: [],
|
||||||
|
...page,
|
||||||
|
}, user);
|
||||||
|
return res.body;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const play = async (user: any, play: any = {}): Promise<any> => {
|
||||||
|
const res = await api('flash/create', {
|
||||||
|
permissions: [],
|
||||||
|
script: 'test',
|
||||||
|
summary: '',
|
||||||
|
title: 'test',
|
||||||
|
...play,
|
||||||
|
}, user);
|
||||||
|
return res.body;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clip = async (user: any, clip: any = {}): Promise<any> => {
|
||||||
|
const res = await api('clips/create', {
|
||||||
|
description: null,
|
||||||
|
isPublic: true,
|
||||||
|
name: 'test',
|
||||||
|
...clip,
|
||||||
|
}, user);
|
||||||
|
return res.body;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const galleryPost = async (user: any, channel: any = {}): Promise<any> => {
|
||||||
|
const res = await api('gallery/posts/create', {
|
||||||
|
description: null,
|
||||||
|
fileIds: [],
|
||||||
|
isSensitive: false,
|
||||||
|
title: 'test',
|
||||||
|
...channel,
|
||||||
|
}, user);
|
||||||
|
return res.body;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const channel = async (user: any, channel: any = {}): Promise<any> => {
|
||||||
|
const res = await api('channels/create', {
|
||||||
|
bannerId: null,
|
||||||
|
description: null,
|
||||||
|
name: 'test',
|
||||||
|
...channel,
|
||||||
|
}, user);
|
||||||
|
return res.body;
|
||||||
|
};
|
||||||
|
|
||||||
interface UploadOptions {
|
interface UploadOptions {
|
||||||
/** Optional, absolute path or relative from ./resources/ */
|
/** Optional, absolute path or relative from ./resources/ */
|
||||||
path?: string | URL;
|
path?: string | URL;
|
||||||
|
@ -196,10 +266,17 @@ export const waitFire = async (user: any, channel: string, trgr: () => any, cond
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status: number, body: any, type: string | null, location: string | null }> => {
|
export type SimpleGetResponse = {
|
||||||
|
status: number,
|
||||||
|
body: any | JSDOM | null,
|
||||||
|
type: string | null,
|
||||||
|
location: string | null
|
||||||
|
};
|
||||||
|
export const simpleGet = async (path: string, accept = '*/*', cookie: any = undefined): Promise<SimpleGetResponse> => {
|
||||||
const res = await relativeFetch(path, {
|
const res = await relativeFetch(path, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: accept,
|
Accept: accept,
|
||||||
|
Cookie: cookie,
|
||||||
},
|
},
|
||||||
redirect: 'manual',
|
redirect: 'manual',
|
||||||
});
|
});
|
||||||
|
@ -208,10 +285,14 @@ export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status:
|
||||||
'application/json; charset=utf-8',
|
'application/json; charset=utf-8',
|
||||||
'application/activity+json; charset=utf-8',
|
'application/activity+json; charset=utf-8',
|
||||||
];
|
];
|
||||||
|
const htmlTypes = [
|
||||||
|
'text/html; charset=utf-8',
|
||||||
|
];
|
||||||
|
|
||||||
const body = jsonTypes.includes(res.headers.get('content-type') ?? '')
|
const body =
|
||||||
? await res.json()
|
jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() :
|
||||||
: null;
|
htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) :
|
||||||
|
null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: res.status,
|
status: res.status,
|
||||||
|
|
Loading…
Reference in a new issue