remove tests
Since I don't use them and they never really worked correctly anyway, there is no real point in keeping them around. Also, removing them allows to shed quite a bit of "weight" in (dev)dependencies.
4
.gitignore
vendored
|
@ -18,10 +18,6 @@
|
|||
node_modules
|
||||
report.*.json
|
||||
|
||||
# Cypress
|
||||
cypress/screenshots
|
||||
cypress/videos
|
||||
|
||||
# config
|
||||
/.config/*
|
||||
!/.config/example.yml
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
url: 'http://misskey.local'
|
||||
|
||||
port: 80
|
||||
|
||||
db:
|
||||
host: postgres
|
||||
port: 5432
|
||||
db: test-misskey
|
||||
user: postgres
|
||||
pass: ''
|
||||
redis:
|
||||
host: redis
|
||||
port: 6379
|
|
@ -17,32 +17,4 @@ pipeline:
|
|||
commands:
|
||||
- yarn install
|
||||
- git diff --exit-code yarn.lock
|
||||
- cp .woodpecker/misskey/test.yml .config
|
||||
- yarn build
|
||||
mocha:
|
||||
when:
|
||||
branch: main
|
||||
event: push
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn mocha
|
||||
e2e:
|
||||
when:
|
||||
branch: main
|
||||
event: push
|
||||
image: cypress/included:10.3.0
|
||||
commands:
|
||||
- npm run start:test &
|
||||
- sleep 30 # wait for server to start
|
||||
- cypress run --browser chrome
|
||||
# TODO: upload screenshots and video artifacts?
|
||||
# would need some kind of storage though
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:13
|
||||
environment:
|
||||
- POSTGRES_DB=test-misskey
|
||||
- POSTGRES_HOST_AUTH_METHOD=trust
|
||||
redis:
|
||||
image: redis:6
|
||||
|
|
|
@ -157,39 +157,6 @@ During development, it is useful to use the `npm run dev` command.
|
|||
This command monitors the server-side and client-side source files and automatically builds them if they are modified.
|
||||
In addition, it will also automatically start the Misskey server process.
|
||||
|
||||
## Testing
|
||||
- Test codes are located in [`/test`](/test).
|
||||
|
||||
### Run test
|
||||
Create a config file.
|
||||
```
|
||||
cp test/test.yml .config/
|
||||
```
|
||||
Prepare DB/Redis for testing.
|
||||
```
|
||||
docker-compose -f test/docker-compose.yml up
|
||||
```
|
||||
Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`.
|
||||
|
||||
Run all test.
|
||||
```
|
||||
npm run test
|
||||
```
|
||||
|
||||
#### Run specify test
|
||||
```
|
||||
npx cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT="./test/tsconfig.json" npx mocha test/foo.ts --require ts-node/register
|
||||
```
|
||||
|
||||
### e2e tests
|
||||
TODO
|
||||
|
||||
## Continuous integration (CI)
|
||||
|
||||
Foundkey uses Woodpecker for executing automated tests and lints.
|
||||
CI runs can be found at [ci.akkoma.dev](https://ci.akkoma.dev/FoundKeyGang/FoundKey)
|
||||
Configuration files are located in `/.woodpecker/`.
|
||||
|
||||
## Vue
|
||||
Misskey uses Vue(v3) as its front-end framework.
|
||||
- Use TypeScript functionality.
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import { defineConfig } from 'cypress'
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
setupNodeEvents(on, config) {
|
||||
return require('./cypress/plugins/index.js')(on, config)
|
||||
},
|
||||
baseUrl: 'http://localhost:61812',
|
||||
},
|
||||
})
|
|
@ -1,149 +0,0 @@
|
|||
describe('Before setup instance', () => {
|
||||
beforeEach(() => {
|
||||
cy.resetState();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
|
||||
// waitを入れることでそれを防止できる
|
||||
cy.wait(1000);
|
||||
});
|
||||
|
||||
it('successfully loads', () => {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('setup instance', () => {
|
||||
cy.visit('/');
|
||||
|
||||
cy.intercept('POST', '/api/admin/accounts/create').as('signup');
|
||||
|
||||
cy.get('[data-cy-admin-username] input').type('admin');
|
||||
cy.get('[data-cy-admin-password] input').type('admin1234');
|
||||
cy.get('[data-cy-admin-ok]').click();
|
||||
|
||||
// なぜか動かない
|
||||
//cy.wait('@signup').should('have.property', 'response.statusCode');
|
||||
cy.wait('@signup');
|
||||
});
|
||||
});
|
||||
|
||||
describe('After setup instance', () => {
|
||||
beforeEach(() => {
|
||||
cy.resetState();
|
||||
|
||||
// インスタンス初期セットアップ
|
||||
cy.registerUser('admin', 'pass', true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
|
||||
// waitを入れることでそれを防止できる
|
||||
cy.wait(1000);
|
||||
});
|
||||
|
||||
it('successfully loads', () => {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('signup', () => {
|
||||
cy.visit('/');
|
||||
|
||||
cy.intercept('POST', '/api/signup').as('signup');
|
||||
|
||||
cy.get('[data-cy-signup]').click();
|
||||
cy.get('[data-cy-signup-username] input').type('alice');
|
||||
cy.get('[data-cy-signup-password] input').type('alice1234');
|
||||
cy.get('[data-cy-signup-password-retype] input').type('alice1234');
|
||||
cy.get('[data-cy-signup-submit]').click();
|
||||
|
||||
cy.wait('@signup');
|
||||
});
|
||||
});
|
||||
|
||||
describe('After user signup', () => {
|
||||
beforeEach(() => {
|
||||
cy.resetState();
|
||||
|
||||
// インスタンス初期セットアップ
|
||||
cy.registerUser('admin', 'pass', true);
|
||||
|
||||
// ユーザー作成
|
||||
cy.registerUser('alice', 'alice1234');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
|
||||
// waitを入れることでそれを防止できる
|
||||
cy.wait(1000);
|
||||
});
|
||||
|
||||
it('successfully loads', () => {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('signin', () => {
|
||||
cy.visit('/');
|
||||
|
||||
cy.intercept('POST', '/api/signin').as('signin');
|
||||
|
||||
cy.get('[data-cy-signin]').click();
|
||||
cy.get('[data-cy-signin-username] input').type('alice');
|
||||
// Enterキーでサインインできるかの確認も兼ねる
|
||||
cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
|
||||
|
||||
cy.wait('@signin');
|
||||
});
|
||||
|
||||
it('suspend', function() {
|
||||
cy.request('POST', '/api/admin/suspend-user', {
|
||||
i: this.admin.token,
|
||||
userId: this.alice.id,
|
||||
});
|
||||
|
||||
cy.visit('/');
|
||||
|
||||
cy.get('[data-cy-signin]').click();
|
||||
cy.get('[data-cy-signin-username] input').type('alice');
|
||||
cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
|
||||
|
||||
// TODO: cypressにブラウザの言語指定できる機能が実装され次第英語のみテストするようにする
|
||||
cy.contains(/アカウントが凍結されています|This account has been suspended due to/gi);
|
||||
});
|
||||
});
|
||||
|
||||
describe('After user singed in', () => {
|
||||
beforeEach(() => {
|
||||
cy.resetState();
|
||||
|
||||
// インスタンス初期セットアップ
|
||||
cy.registerUser('admin', 'pass', true);
|
||||
|
||||
// ユーザー作成
|
||||
cy.registerUser('alice', 'alice1234');
|
||||
|
||||
cy.login('alice', 'alice1234');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
|
||||
// waitを入れることでそれを防止できる
|
||||
cy.wait(1000);
|
||||
});
|
||||
|
||||
it('successfully loads', () => {
|
||||
cy.get('[data-cy-open-post-form]').should('be.visible');
|
||||
});
|
||||
|
||||
it('note', () => {
|
||||
cy.get('[data-cy-open-post-form]').click();
|
||||
cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
|
||||
cy.get('[data-cy-open-post-form-submit]').click();
|
||||
|
||||
cy.contains('Hello, Misskey!');
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: 投稿フォームの公開範囲指定のテスト
|
||||
// TODO: 投稿フォームのファイル添付のテスト
|
||||
// TODO: 投稿フォームのハッシュタグ保持フィールドのテスト
|
|
@ -1,65 +0,0 @@
|
|||
describe('After user signed in', () => {
|
||||
beforeEach(() => {
|
||||
cy.resetState();
|
||||
cy.viewport('macbook-16');
|
||||
|
||||
// インスタンス初期セットアップ
|
||||
cy.registerUser('admin', 'pass', true);
|
||||
|
||||
// ユーザー作成
|
||||
cy.registerUser('alice', 'alice1234');
|
||||
|
||||
cy.login('alice', 'alice1234');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
|
||||
// waitを入れることでそれを防止できる
|
||||
cy.wait(1000);
|
||||
});
|
||||
|
||||
it('widget edit toggle is visible', () => {
|
||||
cy.get('.mk-widget-edit').should('be.visible');
|
||||
});
|
||||
|
||||
it('widget select should be visible in edit mode', () => {
|
||||
cy.get('.mk-widget-edit').click();
|
||||
cy.get('.mk-widget-select').should('be.visible');
|
||||
});
|
||||
|
||||
it('first widget should be removed', () => {
|
||||
cy.get('.mk-widget-edit').click();
|
||||
cy.get('.customize-container:first-child .remove._button').click();
|
||||
cy.get('.customize-container').should('have.length', 2);
|
||||
});
|
||||
|
||||
function buildWidgetTest(widgetName) {
|
||||
it(`${widgetName} widget should get added`, () => {
|
||||
cy.get('.mk-widget-edit').click();
|
||||
cy.get('.mk-widget-select select').select(widgetName, { force: true });
|
||||
cy.get('.bg._modalBg.transparent').click({ multiple: true, force: true });
|
||||
cy.get('.mk-widget-add').click({ force: true });
|
||||
cy.get(`.mkw-${widgetName}`).should('exist');
|
||||
});
|
||||
}
|
||||
|
||||
buildWidgetTest('memo');
|
||||
buildWidgetTest('notifications');
|
||||
buildWidgetTest('timeline');
|
||||
buildWidgetTest('calendar');
|
||||
buildWidgetTest('rss');
|
||||
buildWidgetTest('trends');
|
||||
buildWidgetTest('clock');
|
||||
buildWidgetTest('activity');
|
||||
buildWidgetTest('photos');
|
||||
buildWidgetTest('digitalClock');
|
||||
buildWidgetTest('federation');
|
||||
buildWidgetTest('postForm');
|
||||
buildWidgetTest('slideshow');
|
||||
buildWidgetTest('serverMetric');
|
||||
buildWidgetTest('onlineUsers');
|
||||
buildWidgetTest('jobQueue');
|
||||
buildWidgetTest('button');
|
||||
buildWidgetTest('aiscript');
|
||||
buildWidgetTest('aichan');
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
|
||||
Cypress.Commands.add('resetState', () => {
|
||||
cy.window(win => {
|
||||
win.indexedDB.deleteDatabase('keyval-store');
|
||||
});
|
||||
cy.request('POST', '/api/reset-db').as('reset');
|
||||
cy.get('@reset').its('status').should('equal', 204);
|
||||
cy.reload(true);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('registerUser', (username, password, isAdmin = false) => {
|
||||
const route = isAdmin ? '/api/admin/accounts/create' : '/api/signup';
|
||||
|
||||
cy.request('POST', route, {
|
||||
username: username,
|
||||
password: password,
|
||||
}).its('body').as(username);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('login', (username, password) => {
|
||||
cy.visit('/');
|
||||
|
||||
cy.intercept('POST', '/api/signin').as('signin');
|
||||
|
||||
cy.get('[data-cy-signin]').click();
|
||||
cy.get('[data-cy-signin-username] input').type(username);
|
||||
cy.get('[data-cy-signin-password] input').type(`${password}{enter}`);
|
||||
|
||||
cy.wait('@signin').as('signedIn');
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
Cypress.on('uncaught:exception', (err, runnable) => {
|
||||
if ([
|
||||
// Chrome
|
||||
'ResizeObserver loop limit exceeded',
|
||||
|
||||
// Firefox
|
||||
'ResizeObserver loop completed with undelivered notifications',
|
||||
].some(msg => err.message.includes(msg))) {
|
||||
return false;
|
||||
}
|
||||
});
|
|
@ -13,7 +13,6 @@
|
|||
"build": "yarn workspaces foreach --topological run build && yarn run gulp",
|
||||
"build-parallel": "yarn workspaces foreach --parallel --topological run build && yarn run gulp",
|
||||
"start": "yarn workspace backend run start",
|
||||
"start:test": "yarn workspace backend run start:test",
|
||||
"init": "yarn migrate",
|
||||
"migrate": "yarn workspace backend run migrate",
|
||||
"migrateandstart": "yarn migrate && yarn start",
|
||||
|
@ -21,11 +20,6 @@
|
|||
"watch": "yarn dev",
|
||||
"dev": "node ./scripts/dev.mjs",
|
||||
"lint": "yarn workspaces foreach run lint",
|
||||
"cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts",
|
||||
"cy:run": "cypress run",
|
||||
"e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
|
||||
"mocha": "yarn workspace backend run mocha",
|
||||
"test": "yarn mocha",
|
||||
"format": "gulp format",
|
||||
"clean": "node ./scripts/clean.mjs",
|
||||
"clean-all": "node ./scripts/clean-all.mjs",
|
||||
|
@ -49,8 +43,6 @@
|
|||
"@types/gulp-rename": "2.0.1",
|
||||
"@typescript-eslint/parser": "^5.46.1",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "10.3.0",
|
||||
"start-server-and-test": "1.14.0",
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"packageManager": "yarn@3.4.1"
|
||||
|
|
|
@ -8,11 +8,8 @@
|
|||
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
|
||||
"watch": "node watch.mjs",
|
||||
"lint": "tsc --noEmit --skipLibCheck && eslint src --ext .ts",
|
||||
"mocha": "NODE_ENV=test mocha",
|
||||
"migrate": "yarn exec typeorm migration:run -d ormconfig.js",
|
||||
"start": "node --experimental-json-modules ./built/index.js",
|
||||
"start:test": "NODE_ENV=test node --experimental-json-modules ./built/index.js",
|
||||
"test": "npm run mocha"
|
||||
"start": "node --experimental-json-modules ./built/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bull-board/api": "^4.3.1",
|
||||
|
@ -71,7 +68,6 @@
|
|||
"koa-views": "7.0.2",
|
||||
"mfm-js": "0.23.3",
|
||||
"mime-types": "2.1.35",
|
||||
"mocha": "10.2.0",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.2.6",
|
||||
|
@ -139,7 +135,6 @@
|
|||
"@types/koa__cors": "3.1.1",
|
||||
"@types/koa__multer": "2.0.4",
|
||||
"@types/koa__router": "8.0.11",
|
||||
"@types/mocha": "9.1.1",
|
||||
"@types/node": "18.7.16",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/nodemailer": "6.4.5",
|
||||
|
|
|
@ -70,7 +70,6 @@ import { entities as charts } from '@/services/chart/entities.js';
|
|||
import { Webhook } from '@/models/entities/webhook.js';
|
||||
import { getRedisOptions } from '@/config/redis.js';
|
||||
import { dbLogger } from './logger.js';
|
||||
import { redisClient } from './redis.js';
|
||||
|
||||
const sqlLogger = dbLogger.createSubLogger('sql');
|
||||
|
||||
|
@ -209,31 +208,3 @@ export async function initDb(force = false) {
|
|||
await db.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetDb() {
|
||||
const reset = async () => {
|
||||
await redisClient.flushdb();
|
||||
const tables = await db.query(`SELECT relname AS "table"
|
||||
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
|
||||
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
|
||||
AND C.relkind = 'r'
|
||||
AND nspname !~ '^pg_toast';`);
|
||||
for (const table of tables) {
|
||||
await db.query(`DELETE FROM "${table.table}" CASCADE`);
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
try {
|
||||
await reset();
|
||||
} catch (e) {
|
||||
if (i === 3) {
|
||||
throw e;
|
||||
} else {
|
||||
await new Promise(resolve => setTimeout(resolve, SECOND));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -252,7 +252,6 @@ import * as ep___pages_unlike from './endpoints/pages/unlike.js';
|
|||
import * as ep___pages_update from './endpoints/pages/update.js';
|
||||
import * as ep___ping from './endpoints/ping.js';
|
||||
import * as ep___requestResetPassword from './endpoints/request-reset-password.js';
|
||||
import * as ep___resetDb from './endpoints/reset-db.js';
|
||||
import * as ep___resetPassword from './endpoints/reset-password.js';
|
||||
import * as ep___serverInfo from './endpoints/server-info.js';
|
||||
import * as ep___stats from './endpoints/stats.js';
|
||||
|
@ -546,7 +545,6 @@ const eps = [
|
|||
['pages/update', ep___pages_update],
|
||||
['ping', ep___ping],
|
||||
['request-reset-password', ep___requestResetPassword],
|
||||
['reset-db', ep___resetDb],
|
||||
['reset-password', ep___resetPassword],
|
||||
['server-info', ep___serverInfo],
|
||||
['stats', ep___stats],
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import { resetDb } from '@/db/postgre.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import define from '@/server/api/define.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['non-productive'],
|
||||
|
||||
requireCredential: false,
|
||||
|
||||
description: 'Only available when running with <code>NODE_ENV=testing</code>. Reset the database and flush Redis.',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async () => {
|
||||
if (process.env.NODE_ENV !== 'test') throw new ApiError('ACCESS_DENIED');
|
||||
|
||||
await resetDb();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
});
|
|
@ -1,11 +0,0 @@
|
|||
module.exports = {
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
extends: ['../.eslintrc.cjs'],
|
||||
env: {
|
||||
node: true,
|
||||
mocha: true,
|
||||
},
|
||||
};
|
|
@ -1,113 +0,0 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { initDb } from '../built/db/postgre.js';
|
||||
import { initTestDb } from './utils.mjs';
|
||||
|
||||
|
||||
function rndstr(length) {
|
||||
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
const chars_len = 62;
|
||||
|
||||
let str = '';
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
let rand = Math.floor(Math.random() * chars_len);
|
||||
if (rand === chars_len) {
|
||||
rand = chars_len - 1;
|
||||
}
|
||||
str += chars.charAt(rand);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
describe('ActivityPub', () => {
|
||||
before(async () => {
|
||||
//await initTestDb();
|
||||
await initDb();
|
||||
});
|
||||
|
||||
describe('Parse minimum object', () => {
|
||||
const host = 'https://host1.test';
|
||||
const preferredUsername = `${rndstr(8)}`;
|
||||
const actorId = `${host}/users/${preferredUsername.toLowerCase()}`;
|
||||
|
||||
const actor = {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: actorId,
|
||||
type: 'Person',
|
||||
preferredUsername,
|
||||
inbox: `${actorId}/inbox`,
|
||||
outbox: `${actorId}/outbox`,
|
||||
};
|
||||
|
||||
const post = {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: `${host}/users/${rndstr(8)}`,
|
||||
type: 'Note',
|
||||
attributedTo: actor.id,
|
||||
to: 'https://www.w3.org/ns/activitystreams#Public',
|
||||
content: 'あ',
|
||||
};
|
||||
|
||||
it('Minimum Actor', async () => {
|
||||
const { MockResolver } = await import('./misc/mock-resolver.mjs');
|
||||
const { createPerson } = await import('../built/remote/activitypub/models/person.js');
|
||||
|
||||
const resolver = new MockResolver();
|
||||
resolver._register(actor.id, actor);
|
||||
|
||||
const user = await createPerson(actor.id, resolver);
|
||||
|
||||
assert.deepStrictEqual(user.uri, actor.id);
|
||||
assert.deepStrictEqual(user.username, actor.preferredUsername);
|
||||
assert.deepStrictEqual(user.inbox, actor.inbox);
|
||||
});
|
||||
|
||||
it('Minimum Note', async () => {
|
||||
const { MockResolver } = await import('./misc/mock-resolver.mjs');
|
||||
const { createNote } = await import('../built/remote/activitypub/models/note.js');
|
||||
|
||||
const resolver = new MockResolver();
|
||||
resolver._register(actor.id, actor);
|
||||
resolver._register(post.id, post);
|
||||
|
||||
const note = await createNote(post.id, resolver, true);
|
||||
|
||||
assert.deepStrictEqual(note?.uri, post.id);
|
||||
assert.deepStrictEqual(note.visibility, 'public');
|
||||
assert.deepStrictEqual(note.text, post.content);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Truncate long name', () => {
|
||||
const host = 'https://host1.test';
|
||||
const preferredUsername = `${rndstr(8)}`;
|
||||
const actorId = `${host}/users/${preferredUsername.toLowerCase()}`;
|
||||
|
||||
const name = rndstr(129);
|
||||
|
||||
const actor = {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: actorId,
|
||||
type: 'Person',
|
||||
preferredUsername,
|
||||
name,
|
||||
inbox: `${actorId}/inbox`,
|
||||
outbox: `${actorId}/outbox`,
|
||||
};
|
||||
|
||||
it('Actor', async () => {
|
||||
const { MockResolver } = await import('./misc/mock-resolver.mjs');
|
||||
const { createPerson } = await import('../built/remote/activitypub/models/person.js');
|
||||
|
||||
const resolver = new MockResolver();
|
||||
resolver._register(actor.id, actor);
|
||||
|
||||
const user = await createPerson(actor.id, resolver);
|
||||
|
||||
assert.deepStrictEqual(user.name, actor.name.substr(0, 128));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,55 +0,0 @@
|
|||
import * as assert from 'assert';
|
||||
import httpSignature from '@peertube/http-signature';
|
||||
import { genRsaKeyPair } from '../built/misc/gen-key-pair.js';
|
||||
import { createSignedPost, createSignedGet } from '../built/remote/activitypub/ap-request.js';
|
||||
|
||||
export const buildParsedSignature = (signingString, signature, algorithm) => {
|
||||
return {
|
||||
scheme: 'Signature',
|
||||
params: {
|
||||
keyId: 'KeyID', // dummy, not used for verify
|
||||
algorithm: algorithm,
|
||||
headers: [ '(request-target)', 'date', 'host', 'digest' ], // dummy, not used for verify
|
||||
signature: signature,
|
||||
},
|
||||
signingString: signingString,
|
||||
algorithm: algorithm.toUpperCase(),
|
||||
keyId: 'KeyID', // dummy, not used for verify
|
||||
};
|
||||
};
|
||||
|
||||
describe('ap-request', () => {
|
||||
it('createSignedPost with verify', async () => {
|
||||
const keypair = await genRsaKeyPair();
|
||||
const key = { keyId: 'x', 'privateKeyPem': keypair.privateKey };
|
||||
const url = 'https://example.com/inbox';
|
||||
const activity = { a: 1 };
|
||||
const body = JSON.stringify(activity);
|
||||
const headers = {
|
||||
'User-Agent': 'UA',
|
||||
};
|
||||
|
||||
const req = createSignedPost({ key, url, body, additionalHeaders: headers });
|
||||
|
||||
const parsed = buildParsedSignature(req.signingString, req.signature, 'rsa-sha256');
|
||||
|
||||
const result = httpSignature.verifySignature(parsed, keypair.publicKey);
|
||||
assert.deepStrictEqual(result, true);
|
||||
});
|
||||
|
||||
it('createSignedGet with verify', async () => {
|
||||
const keypair = await genRsaKeyPair();
|
||||
const key = { keyId: 'x', 'privateKeyPem': keypair.privateKey };
|
||||
const url = 'https://example.com/outbox';
|
||||
const headers = {
|
||||
'User-Agent': 'UA',
|
||||
};
|
||||
|
||||
const req = createSignedGet({ key, url, additionalHeaders: headers });
|
||||
|
||||
const parsed = buildParsedSignature(req.signingString, req.signature, 'rsa-sha256');
|
||||
|
||||
const result = httpSignature.verifySignature(parsed, keypair.publicKey);
|
||||
assert.deepStrictEqual(result, true);
|
||||
});
|
||||
});
|
|
@ -1,473 +0,0 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as childProcess from 'child_process';
|
||||
import { async, signup, request, post, startServer, shutdownServer } from './utils.mjs';
|
||||
|
||||
describe('API visibility', function() {
|
||||
this.timeout(20*60*1000);
|
||||
|
||||
let p;
|
||||
|
||||
before(async () => {
|
||||
p = await startServer();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
describe('Note visibility', async () => {
|
||||
//#region vars
|
||||
/** protagonist */
|
||||
let alice;
|
||||
/** follower */
|
||||
let follower;
|
||||
/** non-follower */
|
||||
let other;
|
||||
/** non-follower who has been replied to or mentioned */
|
||||
let target;
|
||||
/** actor for which a specified visibility was set */
|
||||
let target2;
|
||||
|
||||
/** public-post */
|
||||
let pub;
|
||||
/** home-post */
|
||||
let home;
|
||||
/** followers-post */
|
||||
let fol;
|
||||
/** specified-post */
|
||||
let spe;
|
||||
|
||||
/** public-reply to target's post */
|
||||
let pubR;
|
||||
/** home-reply to target's post */
|
||||
let homeR;
|
||||
/** followers-reply to target's post */
|
||||
let folR;
|
||||
/** specified-reply to target's post */
|
||||
let speR;
|
||||
|
||||
/** public-mention to target */
|
||||
let pubM;
|
||||
/** home-mention to target */
|
||||
let homeM;
|
||||
/** followers-mention to target */
|
||||
let folM;
|
||||
/** specified-mention to target */
|
||||
let speM;
|
||||
|
||||
/** reply target post */
|
||||
let tgt;
|
||||
//#endregion
|
||||
|
||||
const show = async (noteId, by) => {
|
||||
return await request('/notes/show', {
|
||||
noteId,
|
||||
}, by);
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
//#region prepare
|
||||
// signup
|
||||
alice = await signup({ username: 'alice' });
|
||||
follower = await signup({ username: 'follower' });
|
||||
other = await signup({ username: 'other' });
|
||||
target = await signup({ username: 'target' });
|
||||
target2 = await signup({ username: 'target2' });
|
||||
|
||||
// follow alice <= follower
|
||||
await request('/following/create', { userId: alice.id }, follower);
|
||||
|
||||
// normal posts
|
||||
pub = await post(alice, { text: 'x', visibility: 'public' });
|
||||
home = await post(alice, { text: 'x', visibility: 'home' });
|
||||
fol = await post(alice, { text: 'x', visibility: 'followers' });
|
||||
spe = await post(alice, { text: 'x', visibility: 'specified', visibleUserIds: [target.id] });
|
||||
|
||||
// replies
|
||||
tgt = await post(target, { text: 'y', visibility: 'public' });
|
||||
pubR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'public' });
|
||||
homeR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'home' });
|
||||
folR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'followers' });
|
||||
speR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'specified' });
|
||||
|
||||
// mentions
|
||||
pubM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'public' });
|
||||
homeM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'home' });
|
||||
folM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'followers' });
|
||||
speM = await post(alice, { text: '@target2 x', replyId: tgt.id, visibility: 'specified' });
|
||||
//#endregion
|
||||
});
|
||||
|
||||
//#region show post
|
||||
// public
|
||||
it('[show] public post can be seen by author', async(async () => {
|
||||
const res = await show(pub.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public post can be seen by follower', async(async () => {
|
||||
const res = await show(pub.id, follower);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public post can be seen by non-follower', async(async () => {
|
||||
const res = await show(pub.id, other);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public post can be seen unauthenticated', async(async () => {
|
||||
const res = await show(pub.id, null);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
// home
|
||||
it('[show] home post can be seen by author', async(async () => {
|
||||
const res = await show(home.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home post can be seen by follower', async(async () => {
|
||||
const res = await show(home.id, follower);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home post can be seen by non-follower', async(async () => {
|
||||
const res = await show(home.id, other);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home post can be seen unauthenticated', async(async () => {
|
||||
const res = await show(home.id, null);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
// followers
|
||||
it('[show] followers post can be seen by author', async(async () => {
|
||||
const res = await show(fol.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] followers post can be seen by follower', async(async () => {
|
||||
const res = await show(fol.id, follower);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] followers post is hidden from non-follower', async(async () => {
|
||||
const res = await show(fol.id, other);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] followers post is hidden when unathenticated', async(async () => {
|
||||
const res = await show(fol.id, null);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
// specified
|
||||
it('[show] specified post can be seen by author', async(async () => {
|
||||
const res = await show(spe.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] specified post can be seen by designated user', async(async () => {
|
||||
const res = await show(spe.id, target);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] specified post is hidden from non-specified follower', async(async () => {
|
||||
const res = await show(spe.id, follower);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified post is hidden from non-follower', async(async () => {
|
||||
const res = await show(spe.id, other);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified post is hidden when unauthenticated', async(async () => {
|
||||
const res = await show(spe.id, null);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
//#endregion
|
||||
|
||||
//#region show reply
|
||||
// public
|
||||
it('[show] public reply can be seen by author', async(async () => {
|
||||
const res = await show(pubR.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public reply can be seen by replied to author', async(async () => {
|
||||
const res = await show(pubR.id, target);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public reply can be seen by follower', async(async () => {
|
||||
const res = await show(pubR.id, follower);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public reply can be seen by non-follower', async(async () => {
|
||||
const res = await show(pubR.id, other);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] public reply can be seen unauthenticated', async(async () => {
|
||||
const res = await show(pubR.id, null);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
// home
|
||||
it('[show] home reply can be seen by author', async(async () => {
|
||||
const res = await show(homeR.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home reply can be seen by replied to author', async(async () => {
|
||||
const res = await show(homeR.id, target);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home reply can be seen by follower', async(async () => {
|
||||
const res = await show(homeR.id, follower);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home reply can be seen by non-follower', async(async () => {
|
||||
const res = await show(homeR.id, other);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] home reply can be seen unauthenticated', async(async () => {
|
||||
const res = await show(homeR.id, null);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
// followers
|
||||
it('[show] followers reply can be seen by author', async(async () => {
|
||||
const res = await show(folR.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] followers reply can be seen by replied to author', async(async () => {
|
||||
const res = await show(folR.id, target);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] followers reply can be seen by follower', async(async () => {
|
||||
const res = await show(folR.id, follower);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] followers reply is hidden from non-follower', async(async () => {
|
||||
const res = await show(folR.id, other);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] followers reply is hidden when unauthenticated', async(async () => {
|
||||
const res = await show(folR.id, null);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
// specified
|
||||
it('[show] specified reply can be seen by author', async(async () => {
|
||||
const res = await show(speR.id, alice);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] specified reply can be seen by replied to user', async(async () => {
|
||||
const res = await show(speR.id, target);
|
||||
assert.strictEqual(res.body.text, 'x');
|
||||
}));
|
||||
|
||||
it('[show] specified reply is hidden from follower', async(async () => {
|
||||
const res = await show(speR.id, follower);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified reply is hidden from non-follower', async(async () => {
|
||||
const res = await show(speR.id, other);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified reply is hidden when unauthenticated', async(async () => {
|
||||
const res = await show(speR.id, null);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
//#endregion
|
||||
|
||||
//#region show mention
|
||||
// public
|
||||
it('[show] public-mention can be seen by author', async(async () => {
|
||||
const res = await show(pubM.id, alice);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] public mention can be seen by mentioned', async(async () => {
|
||||
const res = await show(pubM.id, target);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] public mention can be seen by follower', async(async () => {
|
||||
const res = await show(pubM.id, follower);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] public mention can be seen by non-follower', async(async () => {
|
||||
const res = await show(pubM.id, other);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] public mention can be seen unauthenticated', async(async () => {
|
||||
const res = await show(pubM.id, null);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
// home
|
||||
it('[show] home mention can be seen by author', async(async () => {
|
||||
const res = await show(homeM.id, alice);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] home mention can be seen by mentioned', async(async () => {
|
||||
const res = await show(homeM.id, target);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] home mention can be seen by follower', async(async () => {
|
||||
const res = await show(homeM.id, follower);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] home mention can be seen by non-follower', async(async () => {
|
||||
const res = await show(homeM.id, other);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] home mention can be seen unauthenticated', async(async () => {
|
||||
const res = await show(homeM.id, null);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
// followers
|
||||
it('[show] followers mention can be seen by author', async(async () => {
|
||||
const res = await show(folM.id, alice);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] followers mention can be seen by non-follower mentioned', async(async () => {
|
||||
const res = await show(folM.id, target);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] followers mention can be seen by follower', async(async () => {
|
||||
const res = await show(folM.id, follower);
|
||||
assert.strictEqual(res.body.text, '@target x');
|
||||
}));
|
||||
|
||||
it('[show] followers mention is hidden from non-follower', async(async () => {
|
||||
const res = await show(folM.id, other);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] followers mention is hidden when unauthenticated', async(async () => {
|
||||
const res = await show(folM.id, null);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
// specified
|
||||
it('[show] specified mention can be seen by author', async(async () => {
|
||||
const res = await show(speM.id, alice);
|
||||
assert.strictEqual(res.body.text, '@target2 x');
|
||||
}));
|
||||
|
||||
it('[show] specified mention can be seen by specified actor', async(async () => {
|
||||
const res = await show(speM.id, target);
|
||||
assert.strictEqual(res.body.text, '@target2 x');
|
||||
}));
|
||||
|
||||
it('[show] specified mention is hidden from mentioned but not specified actor', async(async () => {
|
||||
const res = await show(speM.id, target2);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified mention is hidden from follower', async(async () => {
|
||||
const res = await show(speM.id, follower);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified mention is hidden from non-follower', async(async () => {
|
||||
const res = await show(speM.id, other);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('[show] specified mention is hidden when unauthenticated', async(async () => {
|
||||
const res = await show(speM.id, null);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
//#endregion
|
||||
|
||||
//#region Home Timeline
|
||||
it('[TL] public post on author home TL', async(async () => {
|
||||
const res = await request('/notes/timeline', { limit: 100 }, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n) => n.id == pub.id);
|
||||
assert.strictEqual(notes[0].text, 'x');
|
||||
}));
|
||||
|
||||
it('[TL] public post absent from non-follower home TL', async(async () => {
|
||||
const res = await request('/notes/timeline', { limit: 100 }, other);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n) => n.id == pub.id);
|
||||
assert.strictEqual(notes.length, 0);
|
||||
}));
|
||||
|
||||
it('[TL] followers post on follower home TL', async(async () => {
|
||||
const res = await request('/notes/timeline', { limit: 100 }, follower);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n) => n.id == fol.id);
|
||||
assert.strictEqual(notes[0].text, 'x');
|
||||
}));
|
||||
//#endregion
|
||||
|
||||
//#region replies timeline
|
||||
it('[TL] followers reply on follower reply TL', async(async () => {
|
||||
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, follower);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n) => n.id == folR.id);
|
||||
assert.strictEqual(notes[0].text, 'x');
|
||||
}));
|
||||
|
||||
it('[TL] followers reply absent from not replied to non-follower reply TL', async(async () => {
|
||||
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, other);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n) => n.id == folR.id);
|
||||
assert.strictEqual(notes.length, 0);
|
||||
}));
|
||||
|
||||
it('[TL] followers reply on replied to actor reply TL', async(async () => {
|
||||
const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, target);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n) => n.id == folR.id);
|
||||
assert.strictEqual(notes[0].text, 'x');
|
||||
}));
|
||||
//#endregion
|
||||
|
||||
//#region MTL
|
||||
it('[TL] followers reply on replied to non-follower mention TL', async(async () => {
|
||||
const res = await request('/notes/mentions', { limit: 100 }, target);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n) => n.id == folR.id);
|
||||
assert.strictEqual(notes[0].text, 'x');
|
||||
}));
|
||||
|
||||
it('[TL] followers mention on mentioned non-follower mention TL', async(async () => {
|
||||
const res = await request('/notes/mentions', { limit: 100 }, target);
|
||||
assert.strictEqual(res.status, 200);
|
||||
const notes = res.body.filter((n) => n.id == folM.id);
|
||||
assert.strictEqual(notes[0].text, '@target x');
|
||||
}));
|
||||
//#endregion
|
||||
});
|
||||
});
|
|
@ -1,83 +0,0 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as childProcess from 'child_process';
|
||||
import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils.mjs';
|
||||
|
||||
describe('API', function() {
|
||||
this.timeout(20*60*1000);
|
||||
|
||||
let p;
|
||||
let alice, bob, carol;
|
||||
|
||||
before(async () => {
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
carol = await signup({ username: 'carol' });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
describe('General validation', () => {
|
||||
it('wrong type', async(async () => {
|
||||
const res = await request('/test', {
|
||||
required: true,
|
||||
string: 42,
|
||||
});
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('missing require param', async(async () => {
|
||||
const res = await request('/test', {
|
||||
string: 'a',
|
||||
});
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('invalid misskey:id (empty string)', async(async () => {
|
||||
const res = await request('/test', {
|
||||
required: true,
|
||||
id: '',
|
||||
});
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('valid misskey:id', async(async () => {
|
||||
const res = await request('/test', {
|
||||
required: true,
|
||||
id: '8wvhjghbxu',
|
||||
});
|
||||
assert.strictEqual(res.status, 200);
|
||||
}));
|
||||
|
||||
it('default value', async(async () => {
|
||||
const res = await request('/test', {
|
||||
required: true,
|
||||
string: 'a',
|
||||
});
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.body.default, 'hello');
|
||||
}));
|
||||
|
||||
it('can set null even if it has default value', async(async () => {
|
||||
const res = await request('/test', {
|
||||
required: true,
|
||||
nullableDefault: null,
|
||||
});
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.body.nullableDefault, null);
|
||||
}));
|
||||
|
||||
it('cannot set undefined if it has default value', async(async () => {
|
||||
const res = await request('/test', {
|
||||
required: true,
|
||||
nullableDefault: undefined,
|
||||
});
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.body.nullableDefault, 'hello');
|
||||
}));
|
||||
});
|
||||
});
|
|
@ -1,85 +0,0 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as childProcess from 'child_process';
|
||||
import { async, signup, request, post, startServer, shutdownServer } from './utils.mjs';
|
||||
|
||||
describe('Block', function() {
|
||||
this.timeout(20*60*1000);
|
||||
|
||||
let p;
|
||||
|
||||
// alice blocks bob
|
||||
let alice, bob, carol;
|
||||
|
||||
before(async () => {
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
carol = await signup({ username: 'carol' });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
it('can block someone', async(async () => {
|
||||
const res = await request('/blocking/create', {
|
||||
userId: bob.id,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
}));
|
||||
|
||||
it('cannot follow if blocked', async(async () => {
|
||||
const res = await request('/following/create', { userId: alice.id }, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'BLOCKED');
|
||||
}));
|
||||
|
||||
it('cannot react to blocking users note', async(async () => {
|
||||
const note = await post(alice, { text: 'hello' });
|
||||
|
||||
const res = await request('/notes/reactions/create', { noteId: note.id, reaction: '👍' }, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'BLOCKED');
|
||||
}));
|
||||
|
||||
it('cannot reply to blocking users note', async(async () => {
|
||||
const note = await post(alice, { text: 'hello' });
|
||||
|
||||
const res = await request('/notes/create', { replyId: note.id, text: 'yo' }, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'BLOCKED');
|
||||
}));
|
||||
|
||||
it('canot renote blocking users note', async(async () => {
|
||||
const note = await post(alice, { text: 'hello' });
|
||||
|
||||
const res = await request('/notes/create', { renoteId: note.id, text: 'yo' }, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
assert.strictEqual(res.body.error.code, 'BLOCKED');
|
||||
}));
|
||||
|
||||
it('cannot include blocked users in user lists');
|
||||
|
||||
it('removes users from user lists');
|
||||
|
||||
it('local timeline does not contain blocked users', async(async () => {
|
||||
const aliceNote = await post(alice);
|
||||
const bobNote = await post(bob);
|
||||
const carolNote = await post(carol);
|
||||
|
||||
const res = await request('/notes/local-timeline', {}, bob);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((note) => note.id === aliceNote.id), false);
|
||||
assert.strictEqual(res.body.some((note) => note.id === bobNote.id), true);
|
||||
assert.strictEqual(res.body.some((note) => note.id === carolNote.id), true);
|
||||
}));
|
||||
});
|
|
@ -1,527 +0,0 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as lolex from '@sinonjs/fake-timers';
|
||||
import TestChart from '../built/services/chart/charts/test.js';
|
||||
import TestGroupedChart from '../built/services/chart/charts/test-grouped.js';
|
||||
import TestUniqueChart from '../built/services/chart/charts/test-unique.js';
|
||||
import TestIntersectionChart from '../built/services/chart/charts/test-intersection.js';
|
||||
import { initDb } from '../built/db/postgre.js';
|
||||
|
||||
describe('Chart', () => {
|
||||
let testChart, testGroupedChart, testUniqueChart, testIntersectionChart, clock;
|
||||
|
||||
beforeEach(async () => {
|
||||
await initDb(true);
|
||||
|
||||
testChart = new TestChart();
|
||||
testGroupedChart = new TestGroupedChart();
|
||||
testUniqueChart = new TestUniqueChart();
|
||||
testIntersectionChart = new TestIntersectionChart();
|
||||
|
||||
clock = lolex.install({
|
||||
now: new Date(Date.UTC(2000, 0, 1, 0, 0, 0)),
|
||||
shouldClearNativeTimers: true,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clock.uninstall();
|
||||
});
|
||||
|
||||
it('Can updates', async () => {
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3, null);
|
||||
const chartDays = await testChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 0, 0],
|
||||
total: [1, 0, 0],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 0, 0],
|
||||
total: [1, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Can updates (dec)', async () => {
|
||||
await testChart.decrement();
|
||||
await testChart.save();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3, null);
|
||||
const chartDays = await testChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [1, 0, 0],
|
||||
inc: [0, 0, 0],
|
||||
total: [-1, 0, 0],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [1, 0, 0],
|
||||
inc: [0, 0, 0],
|
||||
total: [-1, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Empty chart', async () => {
|
||||
const chartHours = await testChart.getChart('hour', 3, null);
|
||||
const chartDays = await testChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [0, 0, 0],
|
||||
total: [0, 0, 0],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [0, 0, 0],
|
||||
total: [0, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Can updates at multiple times at same time', async () => {
|
||||
await testChart.increment();
|
||||
await testChart.increment();
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3, null);
|
||||
const chartDays = await testChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [3, 0, 0],
|
||||
total: [3, 0, 0],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [3, 0, 0],
|
||||
total: [3, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('複数回saveされてもデータの更新は一度だけ', async () => {
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
await testChart.save();
|
||||
await testChart.save();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3, null);
|
||||
const chartDays = await testChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 0, 0],
|
||||
total: [1, 0, 0],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 0, 0],
|
||||
total: [1, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Can updates at different times', async () => {
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
clock.tick('01:00:00');
|
||||
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3, null);
|
||||
const chartDays = await testChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 1, 0],
|
||||
total: [2, 1, 0],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [2, 0, 0],
|
||||
total: [2, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// 仕様上はこうなってほしいけど、実装は難しそうなのでskip
|
||||
/*
|
||||
it('Can updates at different times without save', async () => {
|
||||
await testChart.increment();
|
||||
|
||||
clock.tick('01:00:00');
|
||||
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3, null);
|
||||
const chartDays = await testChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 1, 0],
|
||||
total: [2, 1, 0]
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [2, 0, 0],
|
||||
total: [2, 0, 0]
|
||||
},
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
it('Can padding', async () => {
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
clock.tick('02:00:00');
|
||||
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3, null);
|
||||
const chartDays = await testChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 0, 1],
|
||||
total: [2, 1, 1],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [2, 0, 0],
|
||||
total: [2, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// 要求された範囲にログがひとつもない場合でもパディングできる
|
||||
it('Can padding from past range', async () => {
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
clock.tick('05:00:00');
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3, null);
|
||||
const chartDays = await testChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [0, 0, 0],
|
||||
total: [1, 1, 1],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 0, 0],
|
||||
total: [1, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// 要求された範囲の最も古い箇所に位置するログが存在しない場合でもパディングできる
|
||||
// Issue #3190
|
||||
it('Can padding from past range 2', async () => {
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
clock.tick('05:00:00');
|
||||
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3, null);
|
||||
const chartDays = await testChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 0, 0],
|
||||
total: [2, 1, 1],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [2, 0, 0],
|
||||
total: [2, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Can specify offset', async () => {
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
clock.tick('01:00:00');
|
||||
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3, new Date(Date.UTC(2000, 0, 1, 0, 0, 0)));
|
||||
const chartDays = await testChart.getChart('day', 3, new Date(Date.UTC(2000, 0, 1, 0, 0, 0)));
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 0, 0],
|
||||
total: [1, 0, 0],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [2, 0, 0],
|
||||
total: [2, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Can specify offset (floor time)', async () => {
|
||||
clock.tick('00:30:00');
|
||||
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
clock.tick('01:30:00');
|
||||
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3, new Date(Date.UTC(2000, 0, 1, 0, 0, 0)));
|
||||
const chartDays = await testChart.getChart('day', 3, new Date(Date.UTC(2000, 0, 1, 0, 0, 0)));
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 0, 0],
|
||||
total: [1, 0, 0],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [2, 0, 0],
|
||||
total: [2, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('Grouped', () => {
|
||||
it('Can updates', async () => {
|
||||
await testGroupedChart.increment('alice');
|
||||
await testGroupedChart.save();
|
||||
|
||||
const aliceChartHours = await testGroupedChart.getChart('hour', 3, null, 'alice');
|
||||
const aliceChartDays = await testGroupedChart.getChart('day', 3, null, 'alice');
|
||||
const bobChartHours = await testGroupedChart.getChart('hour', 3, null, 'bob');
|
||||
const bobChartDays = await testGroupedChart.getChart('day', 3, null, 'bob');
|
||||
|
||||
assert.deepStrictEqual(aliceChartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 0, 0],
|
||||
total: [1, 0, 0],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(aliceChartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 0, 0],
|
||||
total: [1, 0, 0],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(bobChartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [0, 0, 0],
|
||||
total: [0, 0, 0],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(bobChartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [0, 0, 0],
|
||||
total: [0, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Unique increment', () => {
|
||||
it('Can updates', async () => {
|
||||
await testUniqueChart.uniqueIncrement('alice');
|
||||
await testUniqueChart.uniqueIncrement('alice');
|
||||
await testUniqueChart.uniqueIncrement('bob');
|
||||
await testUniqueChart.save();
|
||||
|
||||
const chartHours = await testUniqueChart.getChart('hour', 3, null);
|
||||
const chartDays = await testUniqueChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: [2, 0, 0],
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: [2, 0, 0],
|
||||
});
|
||||
});
|
||||
|
||||
describe('Intersection', () => {
|
||||
it('条件が満たされていない場合はカウントされない', async () => {
|
||||
await testIntersectionChart.addA('alice');
|
||||
await testIntersectionChart.addA('bob');
|
||||
await testIntersectionChart.addB('carol');
|
||||
await testIntersectionChart.save();
|
||||
|
||||
const chartHours = await testIntersectionChart.getChart('hour', 3, null);
|
||||
const chartDays = await testIntersectionChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
a: [2, 0, 0],
|
||||
b: [1, 0, 0],
|
||||
aAndB: [0, 0, 0],
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
a: [2, 0, 0],
|
||||
b: [1, 0, 0],
|
||||
aAndB: [0, 0, 0],
|
||||
});
|
||||
});
|
||||
|
||||
it('条件が満たされている場合にカウントされる', async () => {
|
||||
await testIntersectionChart.addA('alice');
|
||||
await testIntersectionChart.addA('bob');
|
||||
await testIntersectionChart.addB('carol');
|
||||
await testIntersectionChart.addB('alice');
|
||||
await testIntersectionChart.save();
|
||||
|
||||
const chartHours = await testIntersectionChart.getChart('hour', 3, null);
|
||||
const chartDays = await testIntersectionChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
a: [2, 0, 0],
|
||||
b: [2, 0, 0],
|
||||
aAndB: [1, 0, 0],
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
a: [2, 0, 0],
|
||||
b: [2, 0, 0],
|
||||
aAndB: [1, 0, 0],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Resync', () => {
|
||||
it('Can resync', async () => {
|
||||
testChart.total = 1;
|
||||
|
||||
await testChart.resync();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3, null);
|
||||
const chartDays = await testChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [0, 0, 0],
|
||||
total: [1, 0, 0],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [0, 0, 0],
|
||||
total: [1, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Can resync (2)', async () => {
|
||||
await testChart.increment();
|
||||
await testChart.save();
|
||||
|
||||
clock.tick('01:00:00');
|
||||
|
||||
testChart.total = 100;
|
||||
|
||||
await testChart.resync();
|
||||
|
||||
const chartHours = await testChart.getChart('hour', 3, null);
|
||||
const chartDays = await testChart.getChart('day', 3, null);
|
||||
|
||||
assert.deepStrictEqual(chartHours, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [0, 1, 0],
|
||||
total: [100, 1, 0],
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(chartDays, {
|
||||
foo: {
|
||||
dec: [0, 0, 0],
|
||||
inc: [1, 0, 0],
|
||||
total: [100, 0, 0],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,864 +0,0 @@
|
|||
/*
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as childProcess from 'child_process';
|
||||
import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils.mjs';
|
||||
|
||||
describe('API: Endpoints', () => {
|
||||
let p;
|
||||
|
||||
let alice, bob, carol;
|
||||
|
||||
before(async () => {
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
carol = await signup({ username: 'carol' });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
describe('signup', () => {
|
||||
it('不正なユーザー名でアカウントが作成できない', async(async () => {
|
||||
const res = await request('/signup', {
|
||||
username: 'test.',
|
||||
password: 'test'
|
||||
});
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('空のパスワードでアカウントが作成できない', async(async () => {
|
||||
const res = await request('/signup', {
|
||||
username: 'test',
|
||||
password: ''
|
||||
});
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('正しくアカウントが作成できる', async(async () => {
|
||||
const me = {
|
||||
username: 'test1',
|
||||
password: 'test1'
|
||||
};
|
||||
|
||||
const res = await request('/signup', me);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.username, me.username);
|
||||
}));
|
||||
|
||||
it('同じユーザー名のアカウントは作成できない', async(async () => {
|
||||
await signup({
|
||||
username: 'test2'
|
||||
});
|
||||
|
||||
const res = await request('/signup', {
|
||||
username: 'test2',
|
||||
password: 'test2'
|
||||
});
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('signin', () => {
|
||||
it('間違ったパスワードでサインインできない', async(async () => {
|
||||
await signup({
|
||||
username: 'test3',
|
||||
password: 'foo'
|
||||
});
|
||||
|
||||
const res = await request('/signin', {
|
||||
username: 'test3',
|
||||
password: 'bar'
|
||||
});
|
||||
|
||||
assert.strictEqual(res.status, 403);
|
||||
}));
|
||||
|
||||
it('クエリをインジェクションできない', async(async () => {
|
||||
await signup({
|
||||
username: 'test4'
|
||||
});
|
||||
|
||||
const res = await request('/signin', {
|
||||
username: 'test4',
|
||||
password: {
|
||||
$gt: ''
|
||||
}
|
||||
});
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('正しい情報でサインインできる', async(async () => {
|
||||
await signup({
|
||||
username: 'test5',
|
||||
password: 'foo'
|
||||
});
|
||||
|
||||
const res = await request('/signin', {
|
||||
username: 'test5',
|
||||
password: 'foo'
|
||||
});
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('i/update', () => {
|
||||
it('アカウント設定を更新できる', async(async () => {
|
||||
const myName = '大室櫻子';
|
||||
const myLocation = '七森中';
|
||||
const myBirthday = '2000-09-07';
|
||||
|
||||
const res = await request('/i/update', {
|
||||
name: myName,
|
||||
location: myLocation,
|
||||
birthday: myBirthday
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.name, myName);
|
||||
assert.strictEqual(res.body.location, myLocation);
|
||||
assert.strictEqual(res.body.birthday, myBirthday);
|
||||
}));
|
||||
|
||||
it('名前を空白にできない', async(async () => {
|
||||
const res = await request('/i/update', {
|
||||
name: ' '
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('誕生日の設定を削除できる', async(async () => {
|
||||
await request('/i/update', {
|
||||
birthday: '2000-09-07'
|
||||
}, alice);
|
||||
|
||||
const res = await request('/i/update', {
|
||||
birthday: null
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.birthday, null);
|
||||
}));
|
||||
|
||||
it('不正な誕生日の形式で怒られる', async(async () => {
|
||||
const res = await request('/i/update', {
|
||||
birthday: '2000/09/07'
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('users/show', () => {
|
||||
it('ユーザーが取得できる', async(async () => {
|
||||
const res = await request('/users/show', {
|
||||
userId: alice.id
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.id, alice.id);
|
||||
}));
|
||||
|
||||
it('ユーザーが存在しなかったら怒る', async(async () => {
|
||||
const res = await request('/users/show', {
|
||||
userId: '000000000000000000000000'
|
||||
});
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('間違ったIDで怒られる', async(async () => {
|
||||
const res = await request('/users/show', {
|
||||
userId: 'kyoppie'
|
||||
});
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('notes/show', () => {
|
||||
it('投稿が取得できる', async(async () => {
|
||||
const myPost = await post(alice, {
|
||||
text: 'test'
|
||||
});
|
||||
|
||||
const res = await request('/notes/show', {
|
||||
noteId: myPost.id
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.id, myPost.id);
|
||||
assert.strictEqual(res.body.text, myPost.text);
|
||||
}));
|
||||
|
||||
it('投稿が存在しなかったら怒る', async(async () => {
|
||||
const res = await request('/notes/show', {
|
||||
noteId: '000000000000000000000000'
|
||||
});
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('間違ったIDで怒られる', async(async () => {
|
||||
const res = await request('/notes/show', {
|
||||
noteId: 'kyoppie'
|
||||
});
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('notes/reactions/create', () => {
|
||||
it('リアクションできる', async(async () => {
|
||||
const bobPost = await post(bob);
|
||||
|
||||
const alice = await signup({ username: 'alice' });
|
||||
const res = await request('/notes/reactions/create', {
|
||||
noteId: bobPost.id,
|
||||
reaction: '🚀',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
|
||||
const resNote = await request('/notes/show', {
|
||||
noteId: bobPost.id,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(resNote.status, 200);
|
||||
assert.strictEqual(resNote.body.reactions['🚀'], [alice.id]);
|
||||
}));
|
||||
|
||||
it('自分の投稿にもリアクションできる', async(async () => {
|
||||
const myPost = await post(alice);
|
||||
|
||||
const res = await request('/notes/reactions/create', {
|
||||
noteId: myPost.id,
|
||||
reaction: '🚀',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
}));
|
||||
|
||||
it('二重にリアクションできない', async(async () => {
|
||||
const bobPost = await post(bob);
|
||||
|
||||
await react(alice, bobPost, 'like');
|
||||
|
||||
const res = await request('/notes/reactions/create', {
|
||||
noteId: bobPost.id,
|
||||
reaction: '🚀',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('存在しない投稿にはリアクションできない', async(async () => {
|
||||
const res = await request('/notes/reactions/create', {
|
||||
noteId: '000000000000000000000000',
|
||||
reaction: '🚀',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('空のパラメータで怒られる', async(async () => {
|
||||
const res = await request('/notes/reactions/create', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('間違ったIDで怒られる', async(async () => {
|
||||
const res = await request('/notes/reactions/create', {
|
||||
noteId: 'kyoppie',
|
||||
reaction: '🚀',
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('following/create', () => {
|
||||
it('フォローできる', async(async () => {
|
||||
const res = await request('/following/create', {
|
||||
userId: alice.id
|
||||
}, bob);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
}));
|
||||
|
||||
it('既にフォローしている場合は怒る', async(async () => {
|
||||
const res = await request('/following/create', {
|
||||
userId: alice.id
|
||||
}, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('存在しないユーザーはフォローできない', async(async () => {
|
||||
const res = await request('/following/create', {
|
||||
userId: '000000000000000000000000'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('自分自身はフォローできない', async(async () => {
|
||||
const res = await request('/following/create', {
|
||||
userId: alice.id
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('空のパラメータで怒られる', async(async () => {
|
||||
const res = await request('/following/create', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('間違ったIDで怒られる', async(async () => {
|
||||
const res = await request('/following/create', {
|
||||
userId: 'foo'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('following/delete', () => {
|
||||
it('フォロー解除できる', async(async () => {
|
||||
await request('/following/create', {
|
||||
userId: alice.id
|
||||
}, bob);
|
||||
|
||||
const res = await request('/following/delete', {
|
||||
userId: alice.id
|
||||
}, bob);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
}));
|
||||
|
||||
it('フォローしていない場合は怒る', async(async () => {
|
||||
const res = await request('/following/delete', {
|
||||
userId: alice.id
|
||||
}, bob);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('存在しないユーザーはフォロー解除できない', async(async () => {
|
||||
const res = await request('/following/delete', {
|
||||
userId: '000000000000000000000000'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('自分自身はフォロー解除できない', async(async () => {
|
||||
const res = await request('/following/delete', {
|
||||
userId: alice.id
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('空のパラメータで怒られる', async(async () => {
|
||||
const res = await request('/following/delete', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('間違ったIDで怒られる', async(async () => {
|
||||
const res = await request('/following/delete', {
|
||||
userId: 'kyoppie'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('drive', () => {
|
||||
it('ドライブ情報を取得できる', async(async () => {
|
||||
await uploadFile({
|
||||
userId: alice.id,
|
||||
size: 256
|
||||
});
|
||||
await uploadFile({
|
||||
userId: alice.id,
|
||||
size: 512
|
||||
});
|
||||
await uploadFile({
|
||||
userId: alice.id,
|
||||
size: 1024
|
||||
});
|
||||
const res = await request('/drive', {}, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
expect(res.body).have.property('usage').eql(1792);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('drive/files/create', () => {
|
||||
it('ファイルを作成できる', async(async () => {
|
||||
const res = await uploadFile(alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.name, 'Lenna.png');
|
||||
}));
|
||||
|
||||
it('ファイルに名前を付けられる', async(async () => {
|
||||
const res = await assert.request(server)
|
||||
.post('/drive/files/create')
|
||||
.field('i', alice.token)
|
||||
.field('name', 'Belmond.png')
|
||||
.attach('file', fs.readFileSync(__dirname + '/resources/Lenna.png'), 'Lenna.png');
|
||||
|
||||
expect(res).have.status(200);
|
||||
expect(res.body).be.a('object');
|
||||
expect(res.body).have.property('name').eql('Belmond.png');
|
||||
}));
|
||||
|
||||
it('ファイル無しで怒られる', async(async () => {
|
||||
const res = await request('/drive/files/create', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('SVGファイルを作成できる', async(async () => {
|
||||
const res = await uploadFile(alice, __dirname + '/resources/image.svg');
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.name, 'image.svg');
|
||||
assert.strictEqual(res.body.type, 'image/svg+xml');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('drive/files/update', () => {
|
||||
it('名前を更新できる', async(async () => {
|
||||
const file = await uploadFile(alice);
|
||||
const newName = 'いちごパスタ.png';
|
||||
|
||||
const res = await request('/drive/files/update', {
|
||||
fileId: file.id,
|
||||
name: newName
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.name, newName);
|
||||
}));
|
||||
|
||||
it('他人のファイルは更新できない', async(async () => {
|
||||
const file = await uploadFile(bob);
|
||||
|
||||
const res = await request('/drive/files/update', {
|
||||
fileId: file.id,
|
||||
name: 'いちごパスタ.png'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('親フォルダを更新できる', async(async () => {
|
||||
const file = await uploadFile(alice);
|
||||
const folder = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice)).body;
|
||||
|
||||
const res = await request('/drive/files/update', {
|
||||
fileId: file.id,
|
||||
folderId: folder.id
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.folderId, folder.id);
|
||||
}));
|
||||
|
||||
it('親フォルダを無しにできる', async(async () => {
|
||||
const file = await uploadFile(alice);
|
||||
|
||||
const folder = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice)).body;
|
||||
|
||||
await request('/drive/files/update', {
|
||||
fileId: file.id,
|
||||
folderId: folder.id
|
||||
}, alice);
|
||||
|
||||
const res = await request('/drive/files/update', {
|
||||
fileId: file.id,
|
||||
folderId: null
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.folderId, null);
|
||||
}));
|
||||
|
||||
it('他人のフォルダには入れられない', async(async () => {
|
||||
const file = await uploadFile(alice);
|
||||
const folder = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, bob)).body;
|
||||
|
||||
const res = await request('/drive/files/update', {
|
||||
fileId: file.id,
|
||||
folderId: folder.id
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('存在しないフォルダで怒られる', async(async () => {
|
||||
const file = await uploadFile(alice);
|
||||
|
||||
const res = await request('/drive/files/update', {
|
||||
fileId: file.id,
|
||||
folderId: '000000000000000000000000'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('不正なフォルダIDで怒られる', async(async () => {
|
||||
const file = await uploadFile(alice);
|
||||
|
||||
const res = await request('/drive/files/update', {
|
||||
fileId: file.id,
|
||||
folderId: 'foo'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('ファイルが存在しなかったら怒る', async(async () => {
|
||||
const res = await request('/drive/files/update', {
|
||||
fileId: '000000000000000000000000',
|
||||
name: 'いちごパスタ.png'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('間違ったIDで怒られる', async(async () => {
|
||||
const res = await request('/drive/files/update', {
|
||||
fileId: 'kyoppie',
|
||||
name: 'いちごパスタ.png'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('drive/folders/create', () => {
|
||||
it('フォルダを作成できる', async(async () => {
|
||||
const res = await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.name, 'test');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('drive/folders/update', () => {
|
||||
it('名前を更新できる', async(async () => {
|
||||
const folder = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice)).body;
|
||||
|
||||
const res = await request('/drive/folders/update', {
|
||||
folderId: folder.id,
|
||||
name: 'new name'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.name, 'new name');
|
||||
}));
|
||||
|
||||
it('他人のフォルダを更新できない', async(async () => {
|
||||
const folder = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, bob)).body;
|
||||
|
||||
const res = await request('/drive/folders/update', {
|
||||
folderId: folder.id,
|
||||
name: 'new name'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('親フォルダを更新できる', async(async () => {
|
||||
const folder = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice)).body;
|
||||
const parentFolder = (await request('/drive/folders/create', {
|
||||
name: 'parent'
|
||||
}, alice)).body;
|
||||
|
||||
const res = await request('/drive/folders/update', {
|
||||
folderId: folder.id,
|
||||
parentId: parentFolder.id
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.parentId, parentFolder.id);
|
||||
}));
|
||||
|
||||
it('親フォルダを無しに更新できる', async(async () => {
|
||||
const folder = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice)).body;
|
||||
const parentFolder = (await request('/drive/folders/create', {
|
||||
name: 'parent'
|
||||
}, alice)).body;
|
||||
await request('/drive/folders/update', {
|
||||
folderId: folder.id,
|
||||
parentId: parentFolder.id
|
||||
}, alice);
|
||||
|
||||
const res = await request('/drive/folders/update', {
|
||||
folderId: folder.id,
|
||||
parentId: null
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.parentId, null);
|
||||
}));
|
||||
|
||||
it('他人のフォルダを親フォルダに設定できない', async(async () => {
|
||||
const folder = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice)).body;
|
||||
const parentFolder = (await request('/drive/folders/create', {
|
||||
name: 'parent'
|
||||
}, bob)).body;
|
||||
|
||||
const res = await request('/drive/folders/update', {
|
||||
folderId: folder.id,
|
||||
parentId: parentFolder.id
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('フォルダが循環するような構造にできない', async(async () => {
|
||||
const folder = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice)).body;
|
||||
const parentFolder = (await request('/drive/folders/create', {
|
||||
name: 'parent'
|
||||
}, alice)).body;
|
||||
await request('/drive/folders/update', {
|
||||
folderId: parentFolder.id,
|
||||
parentId: folder.id
|
||||
}, alice);
|
||||
|
||||
const res = await request('/drive/folders/update', {
|
||||
folderId: folder.id,
|
||||
parentId: parentFolder.id
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('フォルダが循環するような構造にできない(再帰的)', async(async () => {
|
||||
const folderA = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice)).body;
|
||||
const folderB = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice)).body;
|
||||
const folderC = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice)).body;
|
||||
await request('/drive/folders/update', {
|
||||
folderId: folderB.id,
|
||||
parentId: folderA.id
|
||||
}, alice);
|
||||
await request('/drive/folders/update', {
|
||||
folderId: folderC.id,
|
||||
parentId: folderB.id
|
||||
}, alice);
|
||||
|
||||
const res = await request('/drive/folders/update', {
|
||||
folderId: folderA.id,
|
||||
parentId: folderC.id
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('フォルダが循環するような構造にできない(自身)', async(async () => {
|
||||
const folderA = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice)).body;
|
||||
|
||||
const res = await request('/drive/folders/update', {
|
||||
folderId: folderA.id,
|
||||
parentId: folderA.id
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('存在しない親フォルダを設定できない', async(async () => {
|
||||
const folder = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice)).body;
|
||||
|
||||
const res = await request('/drive/folders/update', {
|
||||
folderId: folder.id,
|
||||
parentId: '000000000000000000000000'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('不正な親フォルダIDで怒られる', async(async () => {
|
||||
const folder = (await request('/drive/folders/create', {
|
||||
name: 'test'
|
||||
}, alice)).body;
|
||||
|
||||
const res = await request('/drive/folders/update', {
|
||||
folderId: folder.id,
|
||||
parentId: 'foo'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('存在しないフォルダを更新できない', async(async () => {
|
||||
const res = await request('/drive/folders/update', {
|
||||
folderId: '000000000000000000000000'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('不正なフォルダIDで怒られる', async(async () => {
|
||||
const res = await request('/drive/folders/update', {
|
||||
folderId: 'foo'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('messaging/messages/create', () => {
|
||||
it('メッセージを送信できる', async(async () => {
|
||||
const res = await request('/messaging/messages/create', {
|
||||
userId: bob.id,
|
||||
text: 'test'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.text, 'test');
|
||||
}));
|
||||
|
||||
it('自分自身にはメッセージを送信できない', async(async () => {
|
||||
const res = await request('/messaging/messages/create', {
|
||||
userId: alice.id,
|
||||
text: 'Yo'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('存在しないユーザーにはメッセージを送信できない', async(async () => {
|
||||
const res = await request('/messaging/messages/create', {
|
||||
userId: '000000000000000000000000',
|
||||
text: 'test'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('不正なユーザーIDで怒られる', async(async () => {
|
||||
const res = await request('/messaging/messages/create', {
|
||||
userId: 'foo',
|
||||
text: 'test'
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('テキストが無くて怒られる', async(async () => {
|
||||
const res = await request('/messaging/messages/create', {
|
||||
userId: bob.id
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('文字数オーバーで怒られる', async(async () => {
|
||||
const res = await request('/messaging/messages/create', {
|
||||
userId: bob.id,
|
||||
text: '!'.repeat(1001)
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('notes/replies', () => {
|
||||
it('自分に閲覧権限のない投稿は含まれない', async(async () => {
|
||||
const alicePost = await post(alice, {
|
||||
text: 'foo'
|
||||
});
|
||||
|
||||
await post(bob, {
|
||||
replyId: alicePost.id,
|
||||
text: 'bar',
|
||||
visibility: 'specified',
|
||||
visibleUserIds: [alice.id]
|
||||
});
|
||||
|
||||
const res = await request('/notes/replies', {
|
||||
noteId: alicePost.id
|
||||
}, carol);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.length, 0);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('notes/timeline', () => {
|
||||
it('フォロワー限定投稿が含まれる', async(async () => {
|
||||
await request('/following/create', {
|
||||
userId: alice.id
|
||||
}, bob);
|
||||
|
||||
const alicePost = await post(alice, {
|
||||
text: 'foo',
|
||||
visibility: 'followers'
|
||||
});
|
||||
|
||||
const res = await request('/notes/timeline', {}, bob);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.length, 1);
|
||||
assert.strictEqual(res.body[0].id, alicePost.id);
|
||||
}));
|
||||
});
|
||||
});
|
||||
*/
|
|
@ -1,42 +0,0 @@
|
|||
import * as assert from 'assert';
|
||||
|
||||
import { parse } from 'mfm-js';
|
||||
import { extractMentions } from '../built/misc/extract-mentions.js';
|
||||
|
||||
describe('Extract mentions', () => {
|
||||
it('simple', () => {
|
||||
const ast = parse('@foo @bar @baz');
|
||||
const mentions = extractMentions(ast);
|
||||
assert.deepStrictEqual(mentions, [{
|
||||
username: 'foo',
|
||||
acct: '@foo',
|
||||
host: null,
|
||||
}, {
|
||||
username: 'bar',
|
||||
acct: '@bar',
|
||||
host: null,
|
||||
}, {
|
||||
username: 'baz',
|
||||
acct: '@baz',
|
||||
host: null,
|
||||
}]);
|
||||
});
|
||||
|
||||
it('nested', () => {
|
||||
const ast = parse('@foo **@bar** @baz');
|
||||
const mentions = extractMentions(ast);
|
||||
assert.deepStrictEqual(mentions, [{
|
||||
username: 'foo',
|
||||
acct: '@foo',
|
||||
host: null,
|
||||
}, {
|
||||
username: 'bar',
|
||||
acct: '@bar',
|
||||
host: null,
|
||||
}, {
|
||||
username: 'baz',
|
||||
acct: '@baz',
|
||||
host: null,
|
||||
}]);
|
||||
});
|
||||
});
|
|
@ -1,206 +0,0 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as childProcess from 'child_process';
|
||||
import * as openapi from '@redocly/openapi-core';
|
||||
import { async, startServer, signup, post, request, simpleGet, port, shutdownServer } from './utils.mjs';
|
||||
|
||||
// Request Accept
|
||||
const ONLY_AP = 'application/activity+json';
|
||||
const PREFER_AP = 'application/activity+json, */*';
|
||||
const PREFER_HTML = 'text/html, */*';
|
||||
const UNSPECIFIED = '*/*';
|
||||
|
||||
// Response Contet-Type
|
||||
const AP = 'application/activity+json; charset=utf-8';
|
||||
const JSON = 'application/json; charset=utf-8';
|
||||
const HTML = 'text/html; charset=utf-8';
|
||||
|
||||
describe('Fetch resource', function() {
|
||||
this.timeout(20*60*1000);
|
||||
|
||||
let p;
|
||||
|
||||
let alice, alicesPost;
|
||||
|
||||
before(async () => {
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
alicesPost = await post(alice, {
|
||||
text: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
describe('Common', () => {
|
||||
it('meta', async(async () => {
|
||||
const res = await request('/meta', {
|
||||
});
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
}));
|
||||
|
||||
it('GET root', async(async () => {
|
||||
const res = await simpleGet('/');
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, HTML);
|
||||
}));
|
||||
|
||||
it('GET docs', async(async () => {
|
||||
const res = await simpleGet('/docs/ja-JP/about');
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, HTML);
|
||||
}));
|
||||
|
||||
it('GET api-doc', async(async () => {
|
||||
const res = await simpleGet('/api-doc');
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, HTML);
|
||||
}));
|
||||
|
||||
it('GET api.json', async(async () => {
|
||||
const res = await simpleGet('/api.json');
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, JSON);
|
||||
}));
|
||||
|
||||
it('Validate api.json', async(async () => {
|
||||
const config = await openapi.loadConfig();
|
||||
const result = await openapi.bundle({
|
||||
config,
|
||||
ref: `http://localhost:${port}/api.json`,
|
||||
});
|
||||
|
||||
for (const problem of result.problems) {
|
||||
console.log(`${problem.message} - ${problem.location[0]?.pointer}`);
|
||||
}
|
||||
|
||||
assert.strictEqual(result.problems.length, 0);
|
||||
}));
|
||||
|
||||
it('GET favicon.ico', async(async () => {
|
||||
const res = await simpleGet('/favicon.ico');
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, 'image/x-icon');
|
||||
}));
|
||||
|
||||
it('GET apple-touch-icon.png', async(async () => {
|
||||
const res = await simpleGet('/apple-touch-icon.png');
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, 'image/png');
|
||||
}));
|
||||
|
||||
it('GET twemoji svg', async(async () => {
|
||||
const res = await simpleGet('/twemoji/2764.svg');
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, 'image/svg+xml');
|
||||
}));
|
||||
|
||||
it('GET twemoji svg with hyphen', async(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', () => {
|
||||
it('Only AP => AP', async(async () => {
|
||||
const res = await simpleGet(`/@${alice.username}`, ONLY_AP);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, AP);
|
||||
}));
|
||||
|
||||
it('Prefer AP => AP', async(async () => {
|
||||
const res = await simpleGet(`/@${alice.username}`, PREFER_AP);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, AP);
|
||||
}));
|
||||
|
||||
it('Prefer HTML => HTML', async(async () => {
|
||||
const res = await simpleGet(`/@${alice.username}`, PREFER_HTML);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, HTML);
|
||||
}));
|
||||
|
||||
it('Unspecified => HTML', async(async () => {
|
||||
const res = await simpleGet(`/@${alice.username}`, UNSPECIFIED);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, HTML);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('/users/:id', () => {
|
||||
it('Only AP => AP', async(async () => {
|
||||
const res = await simpleGet(`/users/${alice.id}`, ONLY_AP);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, AP);
|
||||
}));
|
||||
|
||||
it('Prefer AP => AP', async(async () => {
|
||||
const res = await simpleGet(`/users/${alice.id}`, PREFER_AP);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, AP);
|
||||
}));
|
||||
|
||||
it('Prefer HTML => Redirect to /@:username', async(async () => {
|
||||
const res = await simpleGet(`/users/${alice.id}`, PREFER_HTML);
|
||||
assert.strictEqual(res.status, 302);
|
||||
assert.strictEqual(res.location, `/@${alice.username}`);
|
||||
}));
|
||||
|
||||
it('Undecided => HTML', async(async () => {
|
||||
const res = await simpleGet(`/users/${alice.id}`, UNSPECIFIED);
|
||||
assert.strictEqual(res.status, 302);
|
||||
assert.strictEqual(res.location, `/@${alice.username}`);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('/notes/:id', () => {
|
||||
it('Only AP => AP', async(async () => {
|
||||
const res = await simpleGet(`/notes/${alicesPost.id}`, ONLY_AP);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, AP);
|
||||
}));
|
||||
|
||||
it('Prefer AP => AP', async(async () => {
|
||||
const res = await simpleGet(`/notes/${alicesPost.id}`, PREFER_AP);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, AP);
|
||||
}));
|
||||
|
||||
it('Prefer HTML => HTML', async(async () => {
|
||||
const res = await simpleGet(`/notes/${alicesPost.id}`, PREFER_HTML);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, HTML);
|
||||
}));
|
||||
|
||||
it('Unspecified => HTML', async(async () => {
|
||||
const res = await simpleGet(`/notes/${alicesPost.id}`, UNSPECIFIED);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, HTML);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Feeds', () => {
|
||||
it('RSS', async(async () => {
|
||||
const res = await simpleGet(`/@${alice.username}.rss`, UNSPECIFIED);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, 'application/rss+xml; charset=utf-8');
|
||||
}));
|
||||
|
||||
it('ATOM', async(async () => {
|
||||
const res = await simpleGet(`/@${alice.username}.atom`, UNSPECIFIED);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, 'application/atom+xml; charset=utf-8');
|
||||
}));
|
||||
|
||||
it('JSON', async(async () => {
|
||||
const res = await simpleGet(`/@${alice.username}.json`, UNSPECIFIED);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.type, 'application/json; charset=utf-8');
|
||||
}));
|
||||
});
|
||||
});
|
|
@ -1,116 +0,0 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as childProcess from 'child_process';
|
||||
import { async, signup, request, post, react, connectStream, startServer, shutdownServer, simpleGet } from './utils.mjs';
|
||||
|
||||
describe('FF visibility', function() {
|
||||
this.timeout(20*60*1000);
|
||||
|
||||
let p;
|
||||
|
||||
let alice, bob, follower;
|
||||
|
||||
before(async () => {
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
follower = await signup({ username: 'follower' });
|
||||
|
||||
await request('/following/create', { userId: alice.id }, follower);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
const visible = (user) => {
|
||||
return async () => {
|
||||
const followingRes = await request('/users/following', {
|
||||
userId: alice.id,
|
||||
}, user);
|
||||
const followersRes = await request('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, user);
|
||||
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.ok(Array.isArray(followingRes.body));
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
assert.ok(Array.isArray(followersRes.body));
|
||||
};
|
||||
};
|
||||
|
||||
const hidden = (user) => {
|
||||
return async () => {
|
||||
const followingRes = await request('/users/following', {
|
||||
userId: alice.id,
|
||||
}, user);
|
||||
const followersRes = await request('/users/followers', {
|
||||
userId: alice.id,
|
||||
}, user);
|
||||
|
||||
assert.strictEqual(followingRes.status, 403);
|
||||
assert.strictEqual(followersRes.status, 403);
|
||||
};
|
||||
};
|
||||
|
||||
describe('public visibility', () => {
|
||||
before(async () => {
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'public',
|
||||
}, alice);
|
||||
});
|
||||
|
||||
it('shows followers and following to self', visible(alice));
|
||||
it('shows followers and following to a follower', visible(follower));
|
||||
it('shows followers and following to a non-follower', visible(bob));
|
||||
it('shows followers and following when unauthenticated', visible(null));
|
||||
|
||||
it('provides followers in ActivityPub representation', async () => {
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json');
|
||||
const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json');
|
||||
assert.strictEqual(followingRes.status, 200);
|
||||
assert.strictEqual(followersRes.status, 200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('followers visibility', () => {
|
||||
before(async () => {
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'followers',
|
||||
}, alice);
|
||||
});
|
||||
|
||||
it('shows followers and following to self', visible(alice));
|
||||
it('shows followers and following to a follower', visible(follower));
|
||||
it('hides followers and following from a non-follower', hidden(bob));
|
||||
it('hides followers and following when unauthenticated', hidden(null));
|
||||
|
||||
it('hides followers from ActivityPub representation', async () => {
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json').catch(res => ({ status: res.statusCode }));
|
||||
const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json').catch(res => ({ status: res.statusCode }));
|
||||
assert.strictEqual(followingRes.status, 403);
|
||||
assert.strictEqual(followersRes.status, 403);
|
||||
});
|
||||
});
|
||||
|
||||
describe('private visibility', () => {
|
||||
before(async () => {
|
||||
await request('/i/update', {
|
||||
ffVisibility: 'private',
|
||||
}, alice);
|
||||
});
|
||||
|
||||
it('shows followers and following to self', visible(alice));
|
||||
it('hides followers and following from a follower', hidden(follower));
|
||||
it('hides followers and following from a non-follower', hidden(bob));
|
||||
it('hides followers and following when unauthenticated', hidden(null));
|
||||
|
||||
it('hides followers from ActivityPub representation', async () => {
|
||||
const followingRes = await simpleGet(`/users/${alice.id}/following`, 'application/activity+json').catch(res => ({ status: res.statusCode }));
|
||||
const followersRes = await simpleGet(`/users/${alice.id}/followers`, 'application/activity+json').catch(res => ({ status: res.statusCode }));
|
||||
assert.strictEqual(followingRes.status, 403);
|
||||
assert.strictEqual(followersRes.status, 403);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,173 +0,0 @@
|
|||
import * as assert from 'assert';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import { getFileInfo } from '../built/misc/get-file-info.js';
|
||||
import { async } from './utils.mjs';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
|
||||
describe('Get file info', () => {
|
||||
it('Empty file', async (async () => {
|
||||
const path = `${_dirname}/resources/emptyfile`;
|
||||
const info = await getFileInfo(path);
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 0,
|
||||
md5: 'd41d8cd98f00b204e9800998ecf8427e',
|
||||
type: {
|
||||
mime: 'application/octet-stream',
|
||||
ext: null,
|
||||
},
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
it('Generic JPEG', async (async () => {
|
||||
const path = `${_dirname}/resources/Lenna.jpg`;
|
||||
const info = await getFileInfo(path);
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 25360,
|
||||
md5: '091b3f259662aa31e2ffef4519951168',
|
||||
type: {
|
||||
mime: 'image/jpeg',
|
||||
ext: 'jpg',
|
||||
},
|
||||
width: 512,
|
||||
height: 512,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
it('Generic APNG', async (async () => {
|
||||
const path = `${_dirname}/resources/anime.png`;
|
||||
const info = await getFileInfo(path);
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 1868,
|
||||
md5: '08189c607bea3b952704676bb3c979e0',
|
||||
type: {
|
||||
mime: 'image/apng',
|
||||
ext: 'apng',
|
||||
},
|
||||
width: 256,
|
||||
height: 256,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
it('Generic AGIF', async (async () => {
|
||||
const path = `${_dirname}/resources/anime.gif`;
|
||||
const info = await getFileInfo(path);
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 2248,
|
||||
md5: '32c47a11555675d9267aee1a86571e7e',
|
||||
type: {
|
||||
mime: 'image/gif',
|
||||
ext: 'gif',
|
||||
},
|
||||
width: 256,
|
||||
height: 256,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
it('PNG with alpha', async (async () => {
|
||||
const path = `${_dirname}/resources/with-alpha.png`;
|
||||
const info = await getFileInfo(path);
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 3772,
|
||||
md5: 'f73535c3e1e27508885b69b10cf6e991',
|
||||
type: {
|
||||
mime: 'image/png',
|
||||
ext: 'png',
|
||||
},
|
||||
width: 256,
|
||||
height: 256,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
it('Generic SVG', async (async () => {
|
||||
const path = `${_dirname}/resources/image.svg`;
|
||||
const info = await getFileInfo(path);
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 505,
|
||||
md5: 'b6f52b4b021e7b92cdd04509c7267965',
|
||||
type: {
|
||||
mime: 'image/svg+xml',
|
||||
ext: 'svg',
|
||||
},
|
||||
width: 256,
|
||||
height: 256,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
it('SVG with XML definition', async (async () => {
|
||||
// https://github.com/misskey-dev/misskey/issues/4413
|
||||
const path = `${_dirname}/resources/with-xml-def.svg`;
|
||||
const info = await getFileInfo(path);
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 544,
|
||||
md5: '4b7a346cde9ccbeb267e812567e33397',
|
||||
type: {
|
||||
mime: 'image/svg+xml',
|
||||
ext: 'svg',
|
||||
},
|
||||
width: 256,
|
||||
height: 256,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
it('Dimension limit', async (async () => {
|
||||
const path = `${_dirname}/resources/25000x25000.png`;
|
||||
const info = await getFileInfo(path);
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 75933,
|
||||
md5: '268c5dde99e17cf8fe09f1ab3f97df56',
|
||||
type: {
|
||||
mime: 'application/octet-stream', // do not treat as image
|
||||
ext: null,
|
||||
},
|
||||
width: 25000,
|
||||
height: 25000,
|
||||
orientation: undefined,
|
||||
});
|
||||
}));
|
||||
|
||||
it('Rotate JPEG', async (async () => {
|
||||
const path = `${_dirname}/resources/rotate.jpg`;
|
||||
const info = await getFileInfo(path);
|
||||
delete info.warnings;
|
||||
delete info.blurhash;
|
||||
assert.deepStrictEqual(info, {
|
||||
size: 12624,
|
||||
md5: '68d5b2d8d1d1acbbce99203e3ec3857e',
|
||||
type: {
|
||||
mime: 'image/jpeg',
|
||||
ext: 'jpg',
|
||||
},
|
||||
width: 512,
|
||||
height: 256,
|
||||
orientation: 8,
|
||||
});
|
||||
}));
|
||||
});
|
|
@ -1,88 +0,0 @@
|
|||
import * as assert from 'assert';
|
||||
|
||||
import { toHtml } from '../built/mfm/to-html.js';
|
||||
import { fromHtml } from '../built/mfm/from-html.js';
|
||||
|
||||
describe('toHtml', () => {
|
||||
it('br', async () => {
|
||||
const input = 'foo\nbar\nbaz';
|
||||
const output = '<p><span>foo<br>bar<br>baz</span></p>';
|
||||
assert.equal(await toHtml(input), output);
|
||||
});
|
||||
|
||||
it('br alt', async () => {
|
||||
const input = 'foo\r\nbar\rbaz';
|
||||
const output = '<p><span>foo<br>bar<br>baz</span></p>';
|
||||
assert.equal(await toHtml(input), output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fromHtml', () => {
|
||||
it('p', () => {
|
||||
assert.deepStrictEqual(fromHtml('<p>a</p><p>b</p>'), 'a\n\nb');
|
||||
});
|
||||
|
||||
it('block element', () => {
|
||||
assert.deepStrictEqual(fromHtml('<div>a</div><div>b</div>'), 'a\nb');
|
||||
});
|
||||
|
||||
it('inline element', () => {
|
||||
assert.deepStrictEqual(fromHtml('<ul><li>a</li><li>b</li></ul>'), 'a\nb');
|
||||
});
|
||||
|
||||
it('block code', () => {
|
||||
assert.deepStrictEqual(fromHtml('<pre><code>a\nb</code></pre>'), '```\na\nb\n```');
|
||||
});
|
||||
|
||||
it('inline code', () => {
|
||||
assert.deepStrictEqual(fromHtml('<code>a</code>'), '`a`');
|
||||
});
|
||||
|
||||
it('quote', () => {
|
||||
assert.deepStrictEqual(fromHtml('<blockquote>a\nb</blockquote>'), '> a\n> b');
|
||||
});
|
||||
|
||||
it('br', () => {
|
||||
assert.deepStrictEqual(fromHtml('<p>abc<br><br/>d</p>'), 'abc\n\nd');
|
||||
});
|
||||
|
||||
it('link with different text', () => {
|
||||
assert.deepStrictEqual(fromHtml('<p>a <a href="https://example.com/b">c</a> d</p>'), 'a [c](https://example.com/b) d');
|
||||
});
|
||||
|
||||
it('link with different text, but not encoded', () => {
|
||||
assert.deepStrictEqual(fromHtml('<p>a <a href="https://example.com/ä">c</a> d</p>'), 'a [c](<https://example.com/ä>) d');
|
||||
});
|
||||
|
||||
it('link with same text', () => {
|
||||
assert.deepStrictEqual(fromHtml('<p>a <a href="https://example.com/b">https://example.com/b</a> d</p>'), 'a https://example.com/b d');
|
||||
});
|
||||
|
||||
it('link with same text, but not encoded', () => {
|
||||
assert.deepStrictEqual(fromHtml('<p>a <a href="https://example.com/ä">https://example.com/ä</a> d</p>'), 'a <https://example.com/ä> d');
|
||||
});
|
||||
|
||||
it('link with no url', () => {
|
||||
assert.deepStrictEqual(fromHtml('<p>a <a href="b">c</a> d</p>'), 'a [c](b) d');
|
||||
});
|
||||
|
||||
it('link without href', () => {
|
||||
assert.deepStrictEqual(fromHtml('<p>a <a>c</a> d</p>'), 'a c d');
|
||||
});
|
||||
|
||||
it('link without text', () => {
|
||||
assert.deepStrictEqual(fromHtml('<p>a <a href="https://example.com/b"></a> d</p>'), 'a https://example.com/b d');
|
||||
});
|
||||
|
||||
it('link without both', () => {
|
||||
assert.deepStrictEqual(fromHtml('<p>a <a></a> d</p>'), 'a d');
|
||||
});
|
||||
|
||||
it('mention', () => {
|
||||
assert.deepStrictEqual(fromHtml('<p>a <a href="https://example.com/@user" class="u-url mention">@user</a> d</p>'), 'a @user@example.com d');
|
||||
});
|
||||
|
||||
it('hashtag', () => {
|
||||
assert.deepStrictEqual(fromHtml('<p>a <a href="https://example.com/tags/a">#a</a> d</p>', ['#a']), 'a #a d');
|
||||
});
|
||||
});
|
|
@ -1,30 +0,0 @@
|
|||
import { Resolver } from '../../built/remote/activitypub/resolver.js';
|
||||
|
||||
export class MockResolver extends Resolver {
|
||||
_rs = new Map();
|
||||
|
||||
async _register(uri, content, type = 'application/activity+json') {
|
||||
this._rs.set(uri, {
|
||||
type,
|
||||
content: typeof content === 'string' ? content : JSON.stringify(content),
|
||||
});
|
||||
}
|
||||
|
||||
async resolve(value) {
|
||||
if (typeof value !== 'string') return value;
|
||||
|
||||
const r = this._rs.get(value);
|
||||
|
||||
if (!r) {
|
||||
throw {
|
||||
name: 'StatusError',
|
||||
statusCode: 404,
|
||||
message: 'Not registed for mock',
|
||||
};
|
||||
}
|
||||
|
||||
const object = JSON.parse(r.content);
|
||||
|
||||
return object;
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as childProcess from 'child_process';
|
||||
import { async, signup, request, post, react, startServer, shutdownServer, waitFire } from './utils.mjs';
|
||||
|
||||
describe('Mute', function() {
|
||||
this.timeout(20*60*1000);
|
||||
|
||||
let p;
|
||||
|
||||
// alice mutes carol
|
||||
let alice, bob, carol;
|
||||
|
||||
before(async () => {
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
carol = await signup({ username: 'carol' });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
it('ミュート作成', async(async () => {
|
||||
const res = await request('/mute/create', {
|
||||
userId: carol.id,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
}));
|
||||
|
||||
it('「自分宛ての投稿」にミュートしているユーザーの投稿が含まれない', async(async () => {
|
||||
const bobNote = await post(bob, { text: '@alice hi' });
|
||||
const carolNote = await post(carol, { text: '@alice hi' });
|
||||
|
||||
const res = await request('/notes/mentions', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((note) => note.id === bobNote.id), true);
|
||||
assert.strictEqual(res.body.some((note) => note.id === carolNote.id), false);
|
||||
}));
|
||||
|
||||
it('ミュートしているユーザーからメンションされても、hasUnreadMentions が true にならない', async(async () => {
|
||||
// 状態リセット
|
||||
await request('/i/read-all-unread-notes', {}, alice);
|
||||
|
||||
await post(carol, { text: '@alice hi' });
|
||||
|
||||
const res = await request('/i', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.body.hasUnreadMentions, false);
|
||||
}));
|
||||
|
||||
it('ミュートしているユーザーからメンションされても、ストリームに unreadMention イベントが流れてこない', async () => {
|
||||
// 状態リセット
|
||||
await request('/i/read-all-unread-notes', {}, alice);
|
||||
|
||||
const fired = await waitFire(alice, 'main', () => post(carol, { text: '@alice hi' }), msg => msg.type === 'unreadMention');
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
|
||||
it('ミュートしているユーザーからメンションされても、ストリームに unreadNotification イベントが流れてこない', async () => {
|
||||
// 状態リセット
|
||||
await request('/i/read-all-unread-notes', {}, alice);
|
||||
await request('/notifications/mark-all-as-read', {}, alice);
|
||||
|
||||
const fired = await waitFire(alice, 'main', () => post(carol, { text: '@alice hi' }), msg => msg.type === 'unreadNotification');
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
|
||||
describe('Timeline', () => {
|
||||
it('タイムラインにミュートしているユーザーの投稿が含まれない', async(async () => {
|
||||
const aliceNote = await post(alice);
|
||||
const bobNote = await post(bob);
|
||||
const carolNote = await post(carol);
|
||||
|
||||
const res = await request('/notes/local-timeline', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((note) => note.id === aliceNote.id), true);
|
||||
assert.strictEqual(res.body.some((note) => note.id === bobNote.id), true);
|
||||
assert.strictEqual(res.body.some((note) => note.id === carolNote.id), false);
|
||||
}));
|
||||
|
||||
it('タイムラインにミュートしているユーザーの投稿のRenoteが含まれない', async(async () => {
|
||||
const aliceNote = await post(alice);
|
||||
const carolNote = await post(carol);
|
||||
const bobNote = await post(bob, {
|
||||
renoteId: carolNote.id,
|
||||
});
|
||||
|
||||
const res = await request('/notes/local-timeline', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((note) => note.id === aliceNote.id), true);
|
||||
assert.strictEqual(res.body.some((note) => note.id === bobNote.id), false);
|
||||
assert.strictEqual(res.body.some((note) => note.id === carolNote.id), false);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Notification', () => {
|
||||
it('通知にミュートしているユーザーの通知が含まれない(リアクション)', async(async () => {
|
||||
const aliceNote = await post(alice);
|
||||
await react(bob, aliceNote, 'like');
|
||||
await react(carol, aliceNote, 'like');
|
||||
|
||||
const res = await request('/i/notifications', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((notification) => notification.userId === bob.id), true);
|
||||
assert.strictEqual(res.body.some((notification) => notification.userId === carol.id), false);
|
||||
}));
|
||||
});
|
||||
});
|
|
@ -1,371 +0,0 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as childProcess from 'child_process';
|
||||
import { Note } from '../built/models/entities/note.js';
|
||||
import { async, signup, request, post, uploadUrl, startServer, shutdownServer, initTestDb, api } from './utils.mjs';
|
||||
|
||||
describe('Note', function() {
|
||||
this.timeout(20*60*1000);
|
||||
|
||||
let p;
|
||||
let Notes;
|
||||
|
||||
let alice, bob;
|
||||
|
||||
before(async () => {
|
||||
p = await startServer();
|
||||
const connection = await initTestDb(true);
|
||||
Notes = connection.getRepository(Note);
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
it('投稿できる', async(async () => {
|
||||
const post = {
|
||||
text: 'test',
|
||||
};
|
||||
|
||||
const res = await request('/notes/create', post, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.createdNote.text, post.text);
|
||||
}));
|
||||
|
||||
it('ファイルを添付できる', async(async () => {
|
||||
const file = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
||||
|
||||
const res = await request('/notes/create', {
|
||||
fileIds: [file.id],
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.deepStrictEqual(res.body.createdNote.fileIds, [file.id]);
|
||||
}));
|
||||
|
||||
it('他人のファイルは無視', async(async () => {
|
||||
const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
||||
|
||||
const res = await request('/notes/create', {
|
||||
text: 'test',
|
||||
fileIds: [file.id],
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.deepStrictEqual(res.body.createdNote.fileIds, []);
|
||||
}));
|
||||
|
||||
it('存在しないファイルは無視', async(async () => {
|
||||
const res = await request('/notes/create', {
|
||||
text: 'test',
|
||||
fileIds: ['000000000000000000000000'],
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.deepStrictEqual(res.body.createdNote.fileIds, []);
|
||||
}));
|
||||
|
||||
it('不正なファイルIDは無視', async(async () => {
|
||||
const res = await request('/notes/create', {
|
||||
fileIds: ['kyoppie'],
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.deepStrictEqual(res.body.createdNote.fileIds, []);
|
||||
}));
|
||||
|
||||
it('返信できる', async(async () => {
|
||||
const bobPost = await post(bob, {
|
||||
text: 'foo',
|
||||
});
|
||||
|
||||
const alicePost = {
|
||||
text: 'bar',
|
||||
replyId: bobPost.id,
|
||||
};
|
||||
|
||||
const res = await request('/notes/create', alicePost, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.createdNote.text, alicePost.text);
|
||||
assert.strictEqual(res.body.createdNote.replyId, alicePost.replyId);
|
||||
assert.strictEqual(res.body.createdNote.reply.text, bobPost.text);
|
||||
}));
|
||||
|
||||
it('renoteできる', async(async () => {
|
||||
const bobPost = await post(bob, {
|
||||
text: 'test',
|
||||
});
|
||||
|
||||
const alicePost = {
|
||||
renoteId: bobPost.id,
|
||||
};
|
||||
|
||||
const res = await request('/notes/create', alicePost, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.createdNote.renoteId, alicePost.renoteId);
|
||||
assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
|
||||
}));
|
||||
|
||||
it('引用renoteできる', async(async () => {
|
||||
const bobPost = await post(bob, {
|
||||
text: 'test',
|
||||
});
|
||||
|
||||
const alicePost = {
|
||||
text: 'test',
|
||||
renoteId: bobPost.id,
|
||||
};
|
||||
|
||||
const res = await request('/notes/create', alicePost, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.createdNote.text, alicePost.text);
|
||||
assert.strictEqual(res.body.createdNote.renoteId, alicePost.renoteId);
|
||||
assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
|
||||
}));
|
||||
|
||||
it('文字数ぎりぎりで怒られない', async(async () => {
|
||||
const post = {
|
||||
text: '!'.repeat(3000),
|
||||
};
|
||||
const res = await request('/notes/create', post, alice);
|
||||
assert.strictEqual(res.status, 200);
|
||||
}));
|
||||
|
||||
it('文字数オーバーで怒られる', async(async () => {
|
||||
const post = {
|
||||
text: '!'.repeat(3001),
|
||||
};
|
||||
const res = await request('/notes/create', post, alice);
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('存在しないリプライ先で怒られる', async(async () => {
|
||||
const post = {
|
||||
text: 'test',
|
||||
replyId: '000000000000000000000000',
|
||||
};
|
||||
const res = await request('/notes/create', post, alice);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('存在しないrenote対象で怒られる', async(async () => {
|
||||
const post = {
|
||||
renoteId: '000000000000000000000000',
|
||||
};
|
||||
const res = await request('/notes/create', post, alice);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('不正なリプライ先IDで怒られる', async(async () => {
|
||||
const post = {
|
||||
text: 'test',
|
||||
replyId: 'foo',
|
||||
};
|
||||
const res = await request('/notes/create', post, alice);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('不正なrenote対象IDで怒られる', async(async () => {
|
||||
const post = {
|
||||
renoteId: 'foo',
|
||||
};
|
||||
const res = await request('/notes/create', post, alice);
|
||||
assert.strictEqual(res.status, 404);
|
||||
}));
|
||||
|
||||
it('存在しないユーザーにメンションできる', async(async () => {
|
||||
const post = {
|
||||
text: '@ghost yo',
|
||||
};
|
||||
|
||||
const res = await request('/notes/create', post, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.createdNote.text, post.text);
|
||||
}));
|
||||
|
||||
it('同じユーザーに複数メンションしても内部的にまとめられる', async(async () => {
|
||||
const post = {
|
||||
text: '@bob @bob @bob yo',
|
||||
};
|
||||
|
||||
const res = await request('/notes/create', post, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.createdNote.text, post.text);
|
||||
|
||||
const noteDoc = await Notes.findOneBy({ id: res.body.createdNote.id });
|
||||
assert.deepStrictEqual(noteDoc.mentions, [bob.id]);
|
||||
}));
|
||||
|
||||
describe('notes/create', () => {
|
||||
it('投票を添付できる', async(async () => {
|
||||
const res = await request('/notes/create', {
|
||||
text: 'test',
|
||||
poll: {
|
||||
choices: ['foo', 'bar'],
|
||||
},
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.createdNote.poll != null, true);
|
||||
}));
|
||||
|
||||
it('投票の選択肢が無くて怒られる', async(async () => {
|
||||
const res = await request('/notes/create', {
|
||||
poll: {},
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('投票の選択肢が無くて怒られる (空の配列)', async(async () => {
|
||||
const res = await request('/notes/create', {
|
||||
poll: {
|
||||
choices: [],
|
||||
},
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('投票の選択肢が1つで怒られる', async(async () => {
|
||||
const res = await request('/notes/create', {
|
||||
poll: {
|
||||
choices: ['Strawberry Pasta'],
|
||||
},
|
||||
}, alice);
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
|
||||
it('投票できる', async(async () => {
|
||||
const { body } = await request('/notes/create', {
|
||||
text: 'test',
|
||||
poll: {
|
||||
choices: ['sakura', 'izumi', 'ako'],
|
||||
},
|
||||
}, alice);
|
||||
|
||||
const res = await request('/notes/polls/vote', {
|
||||
noteId: body.createdNote.id,
|
||||
choice: 1,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
}));
|
||||
|
||||
it('複数投票できない', async(async () => {
|
||||
const { body } = await request('/notes/create', {
|
||||
text: 'test',
|
||||
poll: {
|
||||
choices: ['sakura', 'izumi', 'ako'],
|
||||
},
|
||||
}, alice);
|
||||
|
||||
await request('/notes/polls/vote', {
|
||||
noteId: body.createdNote.id,
|
||||
choice: 0,
|
||||
}, alice);
|
||||
|
||||
const res = await request('/notes/polls/vote', {
|
||||
noteId: body.createdNote.id,
|
||||
choice: 2,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 409);
|
||||
}));
|
||||
|
||||
it('許可されている場合は複数投票できる', async(async () => {
|
||||
const { body } = await request('/notes/create', {
|
||||
text: 'test',
|
||||
poll: {
|
||||
choices: ['sakura', 'izumi', 'ako'],
|
||||
multiple: true,
|
||||
},
|
||||
}, alice);
|
||||
|
||||
await request('/notes/polls/vote', {
|
||||
noteId: body.createdNote.id,
|
||||
choice: 0,
|
||||
}, alice);
|
||||
|
||||
await request('/notes/polls/vote', {
|
||||
noteId: body.createdNote.id,
|
||||
choice: 1,
|
||||
}, alice);
|
||||
|
||||
const res = await request('/notes/polls/vote', {
|
||||
noteId: body.createdNote.id,
|
||||
choice: 2,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 204);
|
||||
}));
|
||||
|
||||
it('締め切られている場合は投票できない', async(async () => {
|
||||
const { body } = await request('/notes/create', {
|
||||
text: 'test',
|
||||
poll: {
|
||||
choices: ['sakura', 'izumi', 'ako'],
|
||||
expiredAfter: 1,
|
||||
},
|
||||
}, alice);
|
||||
|
||||
await new Promise(x => setTimeout(x, 2));
|
||||
|
||||
const res = await request('/notes/polls/vote', {
|
||||
noteId: body.createdNote.id,
|
||||
choice: 1,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 400);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('notes/delete', () => {
|
||||
it('delete a reply', async(async () => {
|
||||
const mainNoteRes = await api('notes/create', {
|
||||
text: 'main post',
|
||||
}, alice);
|
||||
const replyOneRes = await api('notes/create', {
|
||||
text: 'reply one',
|
||||
replyId: mainNoteRes.body.createdNote.id,
|
||||
}, alice);
|
||||
const replyTwoRes = await api('notes/create', {
|
||||
text: 'reply two',
|
||||
replyId: mainNoteRes.body.createdNote.id,
|
||||
}, alice);
|
||||
|
||||
const deleteOneRes = await api('notes/delete', {
|
||||
noteId: replyOneRes.body.createdNote.id,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(deleteOneRes.status, 204);
|
||||
let mainNote = await Notes.findOneBy({ id: mainNoteRes.body.createdNote.id });
|
||||
assert.strictEqual(mainNote.repliesCount, 1);
|
||||
|
||||
const deleteTwoRes = await api('notes/delete', {
|
||||
noteId: replyTwoRes.body.createdNote.id,
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(deleteTwoRes.status, 204);
|
||||
mainNote = await Notes.findOneBy({ id: mainNoteRes.body.createdNote.id });
|
||||
assert.strictEqual(mainNote.repliesCount, 0);
|
||||
}));
|
||||
});
|
||||
});
|
|
@ -1,13 +0,0 @@
|
|||
import * as assert from 'assert';
|
||||
import { query } from '../../built/prelude/url.js';
|
||||
|
||||
describe('url', () => {
|
||||
it('query', () => {
|
||||
const s = query({
|
||||
foo: 'ふぅ',
|
||||
bar: 'b a r',
|
||||
baz: undefined,
|
||||
});
|
||||
assert.deepStrictEqual(s, 'foo=%E3%81%B5%E3%81%85&bar=b%20a%20r');
|
||||
});
|
||||
});
|
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 463 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 505 B |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 544 B |
|
@ -1,58 +0,0 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as childProcess from 'child_process';
|
||||
import * as sinon from 'sinon';
|
||||
import { async, signup, startServer, shutdownServer, initTestDb } from '../utils.mjs';
|
||||
|
||||
describe('Creating a block activity', function() {
|
||||
this.timeout(20*60*1000);
|
||||
|
||||
let p;
|
||||
|
||||
// alice blocks bob
|
||||
let alice, bob, carol;
|
||||
|
||||
before(async () => {
|
||||
await initTestDb();
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
carol = await signup({ username: 'carol' });
|
||||
bob.host = 'http://remote';
|
||||
carol.host = 'http://remote';
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
it('Should federate blocks normally', async(async () => {
|
||||
const createBlock = (await import('../../built/services/blocking/create')).default;
|
||||
const deleteBlock = (await import('../../built/services/blocking/delete')).default;
|
||||
|
||||
const queues = await import('../../built/queue/index');
|
||||
const spy = sinon.spy(queues, 'deliver');
|
||||
await createBlock(alice, bob);
|
||||
assert(spy.calledOnce);
|
||||
await deleteBlock(alice, bob);
|
||||
assert(spy.calledTwice);
|
||||
}));
|
||||
|
||||
it('Should not federate blocks if federateBlocks is false', async () => {
|
||||
const createBlock = (await import('../../built/services/blocking/create')).default;
|
||||
const deleteBlock = (await import('../../built/services/blocking/delete')).default;
|
||||
|
||||
alice.federateBlocks = true;
|
||||
|
||||
const queues = await import('../../built/queue/index');
|
||||
const spy = sinon.spy(queues, 'deliver');
|
||||
await createBlock(alice, carol);
|
||||
await deleteBlock(alice, carol);
|
||||
assert(spy.notCalled);
|
||||
});
|
||||
});
|
|
@ -1,543 +0,0 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as childProcess from 'child_process';
|
||||
import { Following } from '../built/models/entities/following.js';
|
||||
import { connectStream, signup, api, post, startServer, shutdownServer, initTestDb, waitFire } from './utils.mjs';
|
||||
|
||||
describe('Streaming', () => {
|
||||
let p;
|
||||
let Followings;
|
||||
|
||||
const follow = async (follower, followee) => {
|
||||
await Followings.save({
|
||||
id: 'a',
|
||||
createdAt: new Date(),
|
||||
followerId: follower.id,
|
||||
followeeId: followee.id,
|
||||
followerHost: follower.host,
|
||||
followerInbox: null,
|
||||
followerSharedInbox: null,
|
||||
followeeHost: followee.host,
|
||||
followeeInbox: null,
|
||||
followeeSharedInbox: null,
|
||||
});
|
||||
};
|
||||
|
||||
describe('Streaming', function() {
|
||||
this.timeout(20*60*1000);
|
||||
|
||||
// Local users
|
||||
let ayano, kyoko, chitose;
|
||||
|
||||
// Remote users
|
||||
let akari, chinatsu;
|
||||
|
||||
let kyokoNote, list;
|
||||
|
||||
before(async () => {
|
||||
p = await startServer();
|
||||
const connection = await initTestDb(true);
|
||||
Followings = connection.getRepository(Following);
|
||||
|
||||
ayano = await signup({ username: 'ayano' });
|
||||
kyoko = await signup({ username: 'kyoko' });
|
||||
chitose = await signup({ username: 'chitose' });
|
||||
|
||||
akari = await signup({ username: 'akari', host: 'example.com' });
|
||||
chinatsu = await signup({ username: 'chinatsu', host: 'example.com' });
|
||||
|
||||
kyokoNote = await post(kyoko, { text: 'foo' });
|
||||
|
||||
// Follow: ayano => kyoko
|
||||
await api('following/create', { userId: kyoko.id }, ayano);
|
||||
|
||||
// Follow: ayano => akari
|
||||
await follow(ayano, akari);
|
||||
|
||||
// List: chitose => ayano, kyoko
|
||||
list = await api('users/lists/create', {
|
||||
name: 'my list',
|
||||
}, chitose).then(x => x.body);
|
||||
|
||||
await api('users/lists/push', {
|
||||
listId: list.id,
|
||||
userId: ayano.id,
|
||||
}, chitose);
|
||||
|
||||
await api('users/lists/push', {
|
||||
listId: list.id,
|
||||
userId: kyoko.id,
|
||||
}, chitose);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
describe('Events', () => {
|
||||
it('mention event', async () => {
|
||||
const fired = await waitFire(
|
||||
kyoko, 'main', // kyoko:main
|
||||
() => post(ayano, { text: 'foo @kyoko bar' }), // ayano mention => kyoko
|
||||
msg => msg.type === 'mention' && msg.body.userId === ayano.id // wait ayano
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('renote event', async () => {
|
||||
const fired = await waitFire(
|
||||
kyoko, 'main', // kyoko:main
|
||||
() => post(ayano, { renoteId: kyokoNote.id }), // ayano renote
|
||||
msg => msg.type === 'renote' && msg.body.renoteId === kyokoNote.id // wait renote
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Home Timeline', () => {
|
||||
it('自分の投稿が流れる', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'homeTimeline', // ayano:Home
|
||||
() => api('notes/create', { text: 'foo' }, ayano), // ayano posts
|
||||
msg => msg.type === 'note' && msg.body.text === 'foo'
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('フォローしているユーザーの投稿が流れる', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'homeTimeline', // ayano:home
|
||||
() => api('notes/create', { text: 'foo' }, kyoko), // kyoko posts
|
||||
msg => msg.type === 'note' && msg.body.userId === kyoko.id // wait kyoko
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('フォローしていないユーザーの投稿は流れない', async () => {
|
||||
const fired = await waitFire(
|
||||
kyoko, 'homeTimeline', // kyoko:home
|
||||
() => api('notes/create', { text: 'foo' }, ayano), // ayano posts
|
||||
msg => msg.type === 'note' && msg.body.userId === ayano.id // wait ayano
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
|
||||
it('フォローしているユーザーのダイレクト投稿が流れる', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'homeTimeline', // ayano:home
|
||||
() => api('notes/create', { text: 'foo', visibility: 'specified', visibleUserIds: [ayano.id], }, kyoko), // kyoko dm => ayano
|
||||
msg => msg.type === 'note' && msg.body.userId === kyoko.id // wait kyoko
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('フォローしているユーザーでも自分が指定されていないダイレクト投稿は流れない', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'homeTimeline', // ayano:home
|
||||
() => api('notes/create', { text: 'foo', visibility: 'specified', visibleUserIds: [chitose.id], }, kyoko), // kyoko dm => chitose
|
||||
msg => msg.type === 'note' && msg.body.userId === kyoko.id // wait kyoko
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
}); // Home
|
||||
|
||||
describe('Local Timeline', () => {
|
||||
it('自分の投稿が流れる', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'localTimeline', // ayano:Local
|
||||
() => api('notes/create', { text: 'foo' }, ayano), // ayano posts
|
||||
msg => msg.type === 'note' && msg.body.text === 'foo'
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('フォローしていないローカルユーザーの投稿が流れる', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'localTimeline', // ayano:Local
|
||||
() => api('notes/create', { text: 'foo' }, chitose), // chitose posts
|
||||
msg => msg.type === 'note' && msg.body.userId === chitose.id // wait chitose
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('リモートユーザーの投稿は流れない', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'localTimeline', // ayano:Local
|
||||
() => api('notes/create', { text: 'foo' }, chinatsu), // chinatsu posts
|
||||
msg => msg.type === 'note' && msg.body.userId === chinatsu.id // wait chinatsu
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
|
||||
it('フォローしてたとしてもリモートユーザーの投稿は流れない', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'localTimeline', // ayano:Local
|
||||
() => api('notes/create', { text: 'foo' }, akari), // akari posts
|
||||
msg => msg.type === 'note' && msg.body.userId === akari.id // wait akari
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
|
||||
it('ホーム指定の投稿は流れない', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'localTimeline', // ayano:Local
|
||||
() => api('notes/create', { text: 'foo', visibility: 'home' }, kyoko), // kyoko home posts
|
||||
msg => msg.type === 'note' && msg.body.userId === kyoko.id // wait kyoko
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
|
||||
it('フォローしているローカルユーザーのダイレクト投稿は流れない', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'localTimeline', // ayano:Local
|
||||
() => api('notes/create', { text: 'foo', visibility: 'specified', visibleUserIds: [ayano.id] }, kyoko), // kyoko DM => ayano
|
||||
msg => msg.type === 'note' && msg.body.userId === kyoko.id // wait kyoko
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
|
||||
it('フォローしていないローカルユーザーのフォロワー宛て投稿は流れない', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'localTimeline', // ayano:Local
|
||||
() => api('notes/create', { text: 'foo', visibility: 'followers' }, chitose),
|
||||
msg => msg.type === 'note' && msg.body.userId === chitose.id // wait chitose
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Hybrid Timeline', () => {
|
||||
it('自分の投稿が流れる', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'hybridTimeline', // ayano:Hybrid
|
||||
() => api('notes/create', { text: 'foo' }, ayano), // ayano posts
|
||||
msg => msg.type === 'note' && msg.body.text === 'foo'
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('フォローしていないローカルユーザーの投稿が流れる', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'hybridTimeline', // ayano:Hybrid
|
||||
() => api('notes/create', { text: 'foo' }, chitose), // chitose posts
|
||||
msg => msg.type === 'note' && msg.body.userId === chitose.id // wait chitose
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('フォローしているリモートユーザーの投稿が流れる', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'hybridTimeline', // ayano:Hybrid
|
||||
() => api('notes/create', { text: 'foo' }, akari), // akari posts
|
||||
msg => msg.type === 'note' && msg.body.userId === akari.id // wait akari
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('フォローしていないリモートユーザーの投稿は流れない', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'hybridTimeline', // ayano:Hybrid
|
||||
() => api('notes/create', { text: 'foo' }, chinatsu), // chinatsu posts
|
||||
msg => msg.type === 'note' && msg.body.userId === chinatsu.id // wait chinatsu
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
|
||||
it('フォローしているユーザーのダイレクト投稿が流れる', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'hybridTimeline', // ayano:Hybrid
|
||||
() => api('notes/create', { text: 'foo', visibility: 'specified', visibleUserIds: [ayano.id] }, kyoko),
|
||||
msg => msg.type === 'note' && msg.body.userId === kyoko.id // wait kyoko
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('フォローしているユーザーのホーム投稿が流れる', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'hybridTimeline', // ayano:Hybrid
|
||||
() => api('notes/create', { text: 'foo', visibility: 'home' }, kyoko),
|
||||
msg => msg.type === 'note' && msg.body.userId === kyoko.id // wait kyoko
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('フォローしていないローカルユーザーのホーム投稿は流れない', async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'hybridTimeline', // ayano:Hybrid
|
||||
() => api('notes/create', { text: 'foo', visibility: 'home' }, chitose),
|
||||
msg => msg.type === 'note' && msg.body.userId === chitose.id
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
|
||||
it('フォローしていないローカルユーザーのフォロワー宛て投稿は流れない', () => async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'hybridTimeline', // ayano:Hybrid
|
||||
() => api('notes/create', { text: 'foo', visibility: 'followers' }, chitose),
|
||||
msg => msg.type === 'note' && msg.body.userId === chitose.id
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Global Timeline', () => {
|
||||
it('フォローしていないローカルユーザーの投稿が流れる', () => async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'globalTimeline', // ayano:Global
|
||||
() => api('notes/create', { text: 'foo' }, chitose), // chitose posts
|
||||
msg => msg.type === 'note' && msg.body.userId === chitose.id // wait chitose
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('フォローしていないリモートユーザーの投稿が流れる', () => async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'globalTimeline', // ayano:Global
|
||||
() => api('notes/create', { text: 'foo' }, chinatsu), // chinatsu posts
|
||||
msg => msg.type === 'note' && msg.body.userId === chinatsu.id // wait chinatsu
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('ホーム投稿は流れない', () => async () => {
|
||||
const fired = await waitFire(
|
||||
ayano, 'globalTimeline', // ayano:Global
|
||||
() => api('notes/create', { text: 'foo', visibility: 'home' }, kyoko), // kyoko posts
|
||||
msg => msg.type === 'note' && msg.body.userId === kyoko.id // wait kyoko
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UserList Timeline', () => {
|
||||
it('リストに入れているユーザーの投稿が流れる', () => async () => {
|
||||
const fired = await waitFire(
|
||||
chitose, 'userList',
|
||||
() => api('notes/create', { text: 'foo' }, ayano),
|
||||
msg => msg.type === 'note' && msg.body.userId === ayano.id,
|
||||
{ listId: list.id, }
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
it('リストに入れていないユーザーの投稿は流れない', () => async () => {
|
||||
const fired = await waitFire(
|
||||
chitose, 'userList',
|
||||
() => api('notes/create', { text: 'foo' }, chinatsu),
|
||||
msg => msg.type === 'note' && msg.body.userId === chinatsu.id,
|
||||
{ listId: list.id, }
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
|
||||
// #4471
|
||||
it('リストに入れているユーザーのダイレクト投稿が流れる', () => async () => {
|
||||
const fired = await waitFire(
|
||||
chitose, 'userList',
|
||||
() => api('notes/create', { text: 'foo', visibility: 'specified', visibleUserIds: [chitose.id] }, ayano),
|
||||
msg => msg.type === 'note' && msg.body.userId === ayano.id,
|
||||
{ listId: list.id, }
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, true);
|
||||
});
|
||||
|
||||
// #4335
|
||||
it('リストに入れているがフォローはしてないユーザーのフォロワー宛て投稿は流れない', () => async () => {
|
||||
const fired = await waitFire(
|
||||
chitose, 'userList',
|
||||
() => api('notes/create', { text: 'foo', visibility: 'followers' }, kyoko),
|
||||
msg => msg.type === 'note' && msg.body.userId === kyoko.id,
|
||||
{ listId: list.id, }
|
||||
);
|
||||
|
||||
assert.strictEqual(fired, false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Hashtag Timeline', () => {
|
||||
it('指定したハッシュタグの投稿が流れる', () => new Promise(async done => {
|
||||
const ws = await connectStream(chitose, 'hashtag', ({ type, body }) => {
|
||||
if (type == 'note') {
|
||||
assert.deepStrictEqual(body.text, '#foo');
|
||||
ws.close();
|
||||
done();
|
||||
}
|
||||
}, {
|
||||
q: [
|
||||
['foo'],
|
||||
],
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo',
|
||||
});
|
||||
}));
|
||||
|
||||
it('指定したハッシュタグの投稿が流れる (AND)', () => new Promise(async done => {
|
||||
let fooCount = 0;
|
||||
let barCount = 0;
|
||||
let fooBarCount = 0;
|
||||
|
||||
const ws = await connectStream(chitose, 'hashtag', ({ type, body }) => {
|
||||
if (type == 'note') {
|
||||
if (body.text === '#foo') fooCount++;
|
||||
if (body.text === '#bar') barCount++;
|
||||
if (body.text === '#foo #bar') fooBarCount++;
|
||||
}
|
||||
}, {
|
||||
q: [
|
||||
['foo', 'bar'],
|
||||
],
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo',
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#bar',
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo #bar',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
assert.strictEqual(fooCount, 0);
|
||||
assert.strictEqual(barCount, 0);
|
||||
assert.strictEqual(fooBarCount, 1);
|
||||
ws.close();
|
||||
done();
|
||||
}, 3000);
|
||||
}));
|
||||
|
||||
it('指定したハッシュタグの投稿が流れる (OR)', () => new Promise(async done => {
|
||||
let fooCount = 0;
|
||||
let barCount = 0;
|
||||
let fooBarCount = 0;
|
||||
let piyoCount = 0;
|
||||
|
||||
const ws = await connectStream(chitose, 'hashtag', ({ type, body }) => {
|
||||
if (type == 'note') {
|
||||
if (body.text === '#foo') fooCount++;
|
||||
if (body.text === '#bar') barCount++;
|
||||
if (body.text === '#foo #bar') fooBarCount++;
|
||||
if (body.text === '#piyo') piyoCount++;
|
||||
}
|
||||
}, {
|
||||
q: [
|
||||
['foo'],
|
||||
['bar'],
|
||||
],
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo',
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#bar',
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo #bar',
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#piyo',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
assert.strictEqual(fooCount, 1);
|
||||
assert.strictEqual(barCount, 1);
|
||||
assert.strictEqual(fooBarCount, 1);
|
||||
assert.strictEqual(piyoCount, 0);
|
||||
ws.close();
|
||||
done();
|
||||
}, 3000);
|
||||
}));
|
||||
|
||||
it('指定したハッシュタグの投稿が流れる (AND + OR)', () => new Promise(async done => {
|
||||
let fooCount = 0;
|
||||
let barCount = 0;
|
||||
let fooBarCount = 0;
|
||||
let piyoCount = 0;
|
||||
let waaaCount = 0;
|
||||
|
||||
const ws = await connectStream(chitose, 'hashtag', ({ type, body }) => {
|
||||
if (type == 'note') {
|
||||
if (body.text === '#foo') fooCount++;
|
||||
if (body.text === '#bar') barCount++;
|
||||
if (body.text === '#foo #bar') fooBarCount++;
|
||||
if (body.text === '#piyo') piyoCount++;
|
||||
if (body.text === '#waaa') waaaCount++;
|
||||
}
|
||||
}, {
|
||||
q: [
|
||||
['foo', 'bar'],
|
||||
['piyo'],
|
||||
],
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo',
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#bar',
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#foo #bar',
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#piyo',
|
||||
});
|
||||
|
||||
post(chitose, {
|
||||
text: '#waaa',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
assert.strictEqual(fooCount, 0);
|
||||
assert.strictEqual(barCount, 0);
|
||||
assert.strictEqual(fooBarCount, 1);
|
||||
assert.strictEqual(piyoCount, 1);
|
||||
assert.strictEqual(waaaCount, 0);
|
||||
ws.close();
|
||||
done();
|
||||
}, 3000);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,103 +0,0 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as childProcess from 'child_process';
|
||||
import { async, signup, request, post, react, connectStream, startServer, shutdownServer } from './utils.mjs';
|
||||
|
||||
describe('Note thread mute', function() {
|
||||
this.timeout(20*60*1000);
|
||||
|
||||
let p;
|
||||
|
||||
let alice, bob, carol;
|
||||
|
||||
before(async () => {
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
bob = await signup({ username: 'bob' });
|
||||
carol = await signup({ username: 'carol' });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
it('notes/mentions にミュートしているスレッドの投稿が含まれない', async(async () => {
|
||||
const bobNote = await post(bob, { text: '@alice @carol root note' });
|
||||
const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' });
|
||||
|
||||
await request('/notes/thread-muting/create', { noteId: bobNote.id }, alice);
|
||||
|
||||
const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' });
|
||||
const carolReplyWithoutMention = await post(carol, { replyId: aliceReply.id, text: 'child note' });
|
||||
|
||||
const res = await request('/notes/mentions', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((note) => note.id === bobNote.id), false);
|
||||
assert.strictEqual(res.body.some((note) => note.id === carolReply.id), false);
|
||||
assert.strictEqual(res.body.some((note) => note.id === carolReplyWithoutMention.id), false);
|
||||
}));
|
||||
|
||||
it('ミュートしているスレッドからメンションされても、hasUnreadMentions が true にならない', async(async () => {
|
||||
// 状態リセット
|
||||
await request('/i/read-all-unread-notes', {}, alice);
|
||||
|
||||
const bobNote = await post(bob, { text: '@alice @carol root note' });
|
||||
|
||||
await request('/notes/thread-muting/create', { noteId: bobNote.id }, alice);
|
||||
|
||||
const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' });
|
||||
|
||||
const res = await request('/i', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(res.body.hasUnreadMentions, false);
|
||||
}));
|
||||
|
||||
it('ミュートしているスレッドからメンションされても、ストリームに unreadMention イベントが流れてこない', () => new Promise(async done => {
|
||||
// 状態リセット
|
||||
await request('/i/read-all-unread-notes', {}, alice);
|
||||
|
||||
const bobNote = await post(bob, { text: '@alice @carol root note' });
|
||||
|
||||
await request('/notes/thread-muting/create', { noteId: bobNote.id }, alice);
|
||||
|
||||
let fired = false;
|
||||
|
||||
const ws = await connectStream(alice, 'main', async ({ type, body }) => {
|
||||
if (type === 'unreadMention') {
|
||||
if (body === bobNote.id) return;
|
||||
fired = true;
|
||||
}
|
||||
});
|
||||
|
||||
const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' });
|
||||
|
||||
setTimeout(() => {
|
||||
assert.strictEqual(fired, false);
|
||||
ws.close();
|
||||
done();
|
||||
}, 5000);
|
||||
}));
|
||||
|
||||
it('i/notifications にミュートしているスレッドの通知が含まれない', async(async () => {
|
||||
const bobNote = await post(bob, { text: '@alice @carol root note' });
|
||||
const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' });
|
||||
|
||||
await request('/notes/thread-muting/create', { noteId: bobNote.id }, alice);
|
||||
|
||||
const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' });
|
||||
const carolReplyWithoutMention = await post(carol, { replyId: aliceReply.id, text: 'child note' });
|
||||
|
||||
const res = await request('/i/notifications', {}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.some((notification) => notification.note.id === carolReply.id), false);
|
||||
assert.strictEqual(res.body.some((notification) => notification.note.id === carolReplyWithoutMention.id), false);
|
||||
|
||||
// NOTE: bobの投稿はスレッドミュート前に行われたため通知に含まれていてもよい
|
||||
}));
|
||||
});
|
|
@ -1,60 +0,0 @@
|
|||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as childProcess from 'child_process';
|
||||
import { async, signup, request, post, uploadUrl, startServer, shutdownServer } from './utils.mjs';
|
||||
|
||||
describe('users/notes', function() {
|
||||
this.timeout(20*60*1000);
|
||||
|
||||
let p;
|
||||
|
||||
let alice, jpgNote, pngNote, jpgPngNote;
|
||||
|
||||
before(async () => {
|
||||
p = await startServer();
|
||||
alice = await signup({ username: 'alice' });
|
||||
const jpg = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
|
||||
const png = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.png');
|
||||
jpgNote = await post(alice, {
|
||||
fileIds: [jpg.id],
|
||||
});
|
||||
pngNote = await post(alice, {
|
||||
fileIds: [png.id],
|
||||
});
|
||||
jpgPngNote = await post(alice, {
|
||||
fileIds: [jpg.id, png.id],
|
||||
});
|
||||
});
|
||||
|
||||
after(async() => {
|
||||
await shutdownServer(p);
|
||||
});
|
||||
|
||||
it('ファイルタイプ指定 (jpg)', async(async () => {
|
||||
const res = await request('/users/notes', {
|
||||
userId: alice.id,
|
||||
fileType: ['image/jpeg'],
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.length, 2);
|
||||
assert.strictEqual(res.body.some((note) => note.id === jpgNote.id), true);
|
||||
assert.strictEqual(res.body.some((note) => note.id === jpgPngNote.id), true);
|
||||
}));
|
||||
|
||||
it('ファイルタイプ指定 (jpg or png)', async(async () => {
|
||||
const res = await request('/users/notes', {
|
||||
userId: alice.id,
|
||||
fileType: ['image/jpeg', 'image/png'],
|
||||
}, alice);
|
||||
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert.strictEqual(Array.isArray(res.body), true);
|
||||
assert.strictEqual(res.body.length, 3);
|
||||
assert.strictEqual(res.body.some((note) => note.id === jpgNote.id), true);
|
||||
assert.strictEqual(res.body.some((note) => note.id === pngNote.id), true);
|
||||
assert.strictEqual(res.body.some((note) => note.id === jpgPngNote.id), true);
|
||||
}));
|
||||
});
|
|
@ -1,322 +0,0 @@
|
|||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import * as childProcess from 'child_process';
|
||||
import * as http from 'node:http';
|
||||
import { SIGKILL } from 'constants';
|
||||
import WebSocket from 'ws';
|
||||
import * as foundkey from 'foundkey-js';
|
||||
import fetch from 'node-fetch';
|
||||
import FormData from 'form-data';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { loadConfig } from '../built/config/load.js';
|
||||
import { entities } from '../built/db/postgre.js';
|
||||
import got from 'got';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
|
||||
const config = loadConfig();
|
||||
export const port = config.port;
|
||||
|
||||
export const async = (fn) => (done) => {
|
||||
fn().then(() => {
|
||||
done();
|
||||
}, (err) => {
|
||||
done(err);
|
||||
});
|
||||
};
|
||||
|
||||
export const api = async (endpoint, params, me) => {
|
||||
endpoint = endpoint.replace(/^\//, '');
|
||||
|
||||
const auth = me ? { authorization: `Bearer ${me.token}` } : {};
|
||||
|
||||
const res = await got(`http://localhost:${port}/api/${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...auth,
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
retry: {
|
||||
limit: 0,
|
||||
},
|
||||
hooks: {
|
||||
beforeError: [
|
||||
error => {
|
||||
const { response } = error;
|
||||
if (response && response.body) console.warn(response.body);
|
||||
return error;
|
||||
}
|
||||
]
|
||||
},
|
||||
});
|
||||
|
||||
const status = res.statusCode;
|
||||
const body = res.statusCode !== 204 ? await JSON.parse(res.body) : null;
|
||||
|
||||
return {
|
||||
status,
|
||||
body
|
||||
};
|
||||
};
|
||||
|
||||
export const request = async (endpoint, params, me) => {
|
||||
const auth = me ? { authorization: `Bearer ${me.token}` } : {};
|
||||
|
||||
const res = await fetch(`http://localhost:${port}/api${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...auth,
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
|
||||
const status = res.status;
|
||||
const body = res.status !== 204 ? await res.json().catch() : null;
|
||||
|
||||
return {
|
||||
body, status,
|
||||
};
|
||||
};
|
||||
|
||||
export const signup = async (params) => {
|
||||
const q = Object.assign({
|
||||
username: 'test',
|
||||
password: 'test',
|
||||
}, params);
|
||||
|
||||
const res = await api('signup', q);
|
||||
|
||||
return res.body;
|
||||
};
|
||||
|
||||
export const post = async (user, params) => {
|
||||
const q = Object.assign({
|
||||
text: 'test',
|
||||
}, params);
|
||||
|
||||
const res = await api('notes/create', q, user);
|
||||
|
||||
return res.body ? res.body.createdNote : null;
|
||||
};
|
||||
|
||||
export const react = async (user, note, reaction) => {
|
||||
await api('notes/reactions/create', {
|
||||
noteId: note.id,
|
||||
reaction: reaction,
|
||||
}, user);
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload file
|
||||
* @param user User
|
||||
* @param _path Optional, absolute path or relative from ./resources/
|
||||
*/
|
||||
export const uploadFile = async (user, _path) => {
|
||||
const absPath = _path == null ? `${_dirname}/resources/Lenna.jpg` : path.isAbsolute(_path) ? _path : `${_dirname}/resources/${_path}`;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('i', user.token);
|
||||
formData.append('file', fs.createReadStream(absPath));
|
||||
formData.append('force', 'true');
|
||||
|
||||
const res = await got<string>(`http://localhost:${port}/api/drive/files/create`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
retry: {
|
||||
limit: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const body = res.statusCode !== 204 ? await JSON.parse(res.body) : null;
|
||||
|
||||
return body;
|
||||
};
|
||||
|
||||
export const uploadUrl = async (user, url) => {
|
||||
let file;
|
||||
|
||||
const ws = await connectStream(user, 'main', (msg) => {
|
||||
if (msg.type === 'driveFileCreated') {
|
||||
file = msg.body;
|
||||
}
|
||||
});
|
||||
|
||||
await api('drive/files/upload-from-url', {
|
||||
url,
|
||||
force: true,
|
||||
}, user);
|
||||
|
||||
await sleep(5000);
|
||||
ws.close();
|
||||
|
||||
return file;
|
||||
};
|
||||
|
||||
export function connectStream(user, channel, listener, params) {
|
||||
return new Promise((res, rej) => {
|
||||
const ws = new WebSocket(`ws://localhost:${port}/streaming?i=${user.token}`);
|
||||
|
||||
ws.on('open', () => {
|
||||
ws.on('message', data => {
|
||||
const msg = JSON.parse(data.toString());
|
||||
if (msg.type === 'channel' && msg.body.id === 'a') {
|
||||
listener(msg.body);
|
||||
} else if (msg.type === 'connected' && msg.body.id === 'a') {
|
||||
res(ws);
|
||||
}
|
||||
});
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
type: 'connect',
|
||||
body: {
|
||||
channel: channel,
|
||||
id: 'a',
|
||||
pong: true,
|
||||
params: params,
|
||||
},
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const waitFire = async (user, channel, trgr, cond, params) => {
|
||||
return new Promise(async (res, rej) => {
|
||||
let timer;
|
||||
|
||||
let ws;
|
||||
try {
|
||||
ws = await connectStream(user, channel, msg => {
|
||||
if (cond(msg)) {
|
||||
ws.close();
|
||||
if (timer) clearTimeout(timer);
|
||||
res(true);
|
||||
}
|
||||
}, params);
|
||||
} catch (e) {
|
||||
rej(e);
|
||||
}
|
||||
|
||||
if (!ws) return;
|
||||
|
||||
timer = setTimeout(() => {
|
||||
ws.close();
|
||||
res(false);
|
||||
}, 3000);
|
||||
|
||||
try {
|
||||
await trgr();
|
||||
} catch (e) {
|
||||
ws.close();
|
||||
if (timer) clearTimeout(timer);
|
||||
rej(e);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
export const simpleGet = async (path, accept = '*/*') => {
|
||||
// node-fetchだと3xxを取れない
|
||||
return await new Promise((resolve, reject) => {
|
||||
const req = http.request(`http://localhost:${port}${path}`, {
|
||||
headers: {
|
||||
Accept: accept,
|
||||
},
|
||||
}, res => {
|
||||
if (res.statusCode >= 400) {
|
||||
reject(res);
|
||||
} else {
|
||||
resolve({
|
||||
status: res.statusCode,
|
||||
type: res.headers['content-type'],
|
||||
location: res.headers.location,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
};
|
||||
|
||||
export function launchServer(callbackSpawnedProcess, moreProcess = async () => {}) {
|
||||
return (done) => {
|
||||
const p = childProcess.spawn('node', [_dirname + '/../index.js'], {
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
||||
env: { NODE_ENV: 'test', PATH: process.env.PATH },
|
||||
});
|
||||
callbackSpawnedProcess(p);
|
||||
p.on('message', message => {
|
||||
if (message === 'ok') moreProcess().then(() => done()).catch(e => done(e));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export async function initTestDb(justBorrow = false, initEntities) {
|
||||
if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test';
|
||||
|
||||
const db = new DataSource({
|
||||
type: 'postgres',
|
||||
host: config.db.host,
|
||||
port: config.db.port,
|
||||
username: config.db.user,
|
||||
password: config.db.pass,
|
||||
database: config.db.db,
|
||||
synchronize: true && !justBorrow,
|
||||
dropSchema: true && !justBorrow,
|
||||
entities: initEntities || entities,
|
||||
});
|
||||
|
||||
await db.initialize();
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
export function startServer(timeout = 60 * 1000) {
|
||||
return new Promise((res, rej) => {
|
||||
const t = setTimeout(() => {
|
||||
p.kill(SIGKILL);
|
||||
rej('timeout to start');
|
||||
}, timeout);
|
||||
|
||||
const p = childProcess.spawn('node', [_dirname + '/../built/index.js'], {
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
||||
env: { NODE_ENV: 'test', PATH: process.env.PATH },
|
||||
});
|
||||
|
||||
p.on('error', e => rej(e));
|
||||
|
||||
p.on('message', message => {
|
||||
if (message === 'ok') {
|
||||
clearTimeout(t);
|
||||
res(p);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function shutdownServer(p, timeout = 20 * 1000) {
|
||||
return new Promise((res, rej) => {
|
||||
const t = setTimeout(() => {
|
||||
p.kill(SIGKILL);
|
||||
res('force exit');
|
||||
}, timeout);
|
||||
|
||||
p.once('exit', () => {
|
||||
clearTimeout(t);
|
||||
res('exited');
|
||||
});
|
||||
|
||||
p.kill();
|
||||
});
|
||||
}
|
||||
|
||||
export function sleep(msec) {
|
||||
return new Promise(res => {
|
||||
setTimeout(() => {
|
||||
res();
|
||||
}, msec);
|
||||
});
|
||||
}
|
|
@ -65,7 +65,6 @@
|
|||
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
||||
"@typescript-eslint/parser": "^5.46.1",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "10.3.0",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-vue": "^9.8.0"
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
import { expectType } from 'tsd';
|
||||
import * as foundkey from '../src';
|
||||
|
||||
describe('API', () => {
|
||||
test('success', async () => {
|
||||
const cli = new foundkey.api.APIClient({
|
||||
origin: 'https://foundkey.test',
|
||||
credential: 'TOKEN'
|
||||
});
|
||||
const res = await cli.request('meta', { detail: true });
|
||||
expectType<foundkey.entities.DetailedInstanceMetadata>(res);
|
||||
});
|
||||
|
||||
test('conditional respose type (meta)', async () => {
|
||||
const cli = new foundkey.api.APIClient({
|
||||
origin: 'https://foundkey.test',
|
||||
credential: 'TOKEN'
|
||||
});
|
||||
|
||||
const res = await cli.request('meta', { detail: true });
|
||||
expectType<foundkey.entities.DetailedInstanceMetadata>(res);
|
||||
|
||||
const res2 = await cli.request('meta', { detail: false });
|
||||
expectType<foundkey.entities.LiteInstanceMetadata>(res2);
|
||||
|
||||
const res3 = await cli.request('meta', { });
|
||||
expectType<foundkey.entities.LiteInstanceMetadata>(res3);
|
||||
|
||||
const res4 = await cli.request('meta', { detail: true as boolean });
|
||||
expectType<foundkey.entities.LiteInstanceMetadata | foundkey.entities.DetailedInstanceMetadata>(res4);
|
||||
});
|
||||
|
||||
test('conditional respose type (users/show)', async () => {
|
||||
const cli = new foundkey.api.APIClient({
|
||||
origin: 'https://foundkey.test',
|
||||
credential: 'TOKEN'
|
||||
});
|
||||
|
||||
const res = await cli.request('users/show', { userId: 'xxxxxxxx' });
|
||||
expectType<foundkey.entities.UserDetailed>(res);
|
||||
|
||||
const res2 = await cli.request('users/show', { userIds: ['xxxxxxxx'] });
|
||||
expectType<foundkey.entities.UserDetailed[]>(res2);
|
||||
});
|
||||
});
|
|
@ -1,26 +0,0 @@
|
|||
import { expectType } from 'tsd';
|
||||
import * as foundkey from '../src';
|
||||
|
||||
describe('Streaming', () => {
|
||||
test('emit type', async () => {
|
||||
const stream = new foundkey.Stream('https://foundkey.test', { token: 'TOKEN' });
|
||||
const mainChannel = stream.useChannel('main');
|
||||
mainChannel.on('notification', notification => {
|
||||
expectType<foundkey.entities.Notification>(notification);
|
||||
});
|
||||
});
|
||||
|
||||
test('params type', async () => {
|
||||
const stream = new foundkey.Stream('https://foundkey.test', { token: 'TOKEN' });
|
||||
// TODO: 「stream.useChannel の第二引数として受け入れる型が
|
||||
// {
|
||||
// otherparty?: User['id'] | null;
|
||||
// group?: UserGroup['id'] | null;
|
||||
// }
|
||||
// になっている」というテストを行いたいけどtsdでの書き方がわからない
|
||||
const messagingChannel = stream.useChannel('messaging', { otherparty: 'aaa' });
|
||||
messagingChannel.on('message', message => {
|
||||
expectType<foundkey.entities.MessagingMessage>(message);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,212 +0,0 @@
|
|||
import { APIClient, isAPIError } from '../src/api';
|
||||
import { enableFetchMocks } from 'jest-fetch-mock';
|
||||
|
||||
enableFetchMocks();
|
||||
|
||||
function getFetchCall(call: any[]) {
|
||||
const { body, method } = call[1];
|
||||
if (body != null && typeof body != 'string') {
|
||||
throw new Error('invalid body');
|
||||
}
|
||||
return {
|
||||
url: call[0],
|
||||
method: method,
|
||||
body: JSON.parse(body as any)
|
||||
};
|
||||
}
|
||||
|
||||
describe('API', () => {
|
||||
test('success', async () => {
|
||||
fetchMock.resetMocks();
|
||||
fetchMock.mockResponse(async (req) => {
|
||||
const body = await req.json();
|
||||
if (req.method == 'POST' && req.url == 'https://foundkey.test/api/i') {
|
||||
if (body.i === 'TOKEN') {
|
||||
return JSON.stringify({ id: 'foo' });
|
||||
} else {
|
||||
return { status: 400 };
|
||||
}
|
||||
} else {
|
||||
return { status: 404 };
|
||||
}
|
||||
});
|
||||
|
||||
const cli = new APIClient({
|
||||
origin: 'https://foundkey.test',
|
||||
credential: 'TOKEN',
|
||||
});
|
||||
|
||||
const res = await cli.request('i');
|
||||
|
||||
expect(res).toEqual({
|
||||
id: 'foo'
|
||||
});
|
||||
|
||||
expect(getFetchCall(fetchMock.mock.calls[0])).toEqual({
|
||||
url: 'https://foundkey.test/api/i',
|
||||
method: 'POST',
|
||||
body: { i: 'TOKEN' }
|
||||
});
|
||||
});
|
||||
|
||||
test('with params', async () => {
|
||||
fetchMock.resetMocks();
|
||||
fetchMock.mockResponse(async (req) => {
|
||||
const body = await req.json();
|
||||
if (req.method == 'POST' && req.url == 'https://foundkey.test/api/notes/show') {
|
||||
if (body.i === 'TOKEN' && body.noteId === 'aaaaa') {
|
||||
return JSON.stringify({ id: 'foo' });
|
||||
} else {
|
||||
return { status: 400 };
|
||||
}
|
||||
} else {
|
||||
return { status: 404 };
|
||||
}
|
||||
});
|
||||
|
||||
const cli = new APIClient({
|
||||
origin: 'https://foundkey.test',
|
||||
credential: 'TOKEN',
|
||||
});
|
||||
|
||||
const res = await cli.request('notes/show', { noteId: 'aaaaa' });
|
||||
|
||||
expect(res).toEqual({
|
||||
id: 'foo'
|
||||
});
|
||||
|
||||
expect(getFetchCall(fetchMock.mock.calls[0])).toEqual({
|
||||
url: 'https://foundkey.test/api/notes/show',
|
||||
method: 'POST',
|
||||
body: { i: 'TOKEN', noteId: 'aaaaa' }
|
||||
});
|
||||
});
|
||||
|
||||
test('204 No Content で null が返る', async () => {
|
||||
fetchMock.resetMocks();
|
||||
fetchMock.mockResponse(async (req) => {
|
||||
if (req.method == 'POST' && req.url == 'https://foundkey.test/api/reset-password') {
|
||||
return { status: 204 };
|
||||
} else {
|
||||
return { status: 404 };
|
||||
}
|
||||
});
|
||||
|
||||
const cli = new APIClient({
|
||||
origin: 'https://foundkey.test',
|
||||
credential: 'TOKEN',
|
||||
});
|
||||
|
||||
const res = await cli.request('reset-password', { token: 'aaa', password: 'aaa' });
|
||||
|
||||
expect(res).toEqual(null);
|
||||
|
||||
expect(getFetchCall(fetchMock.mock.calls[0])).toEqual({
|
||||
url: 'https://foundkey.test/api/reset-password',
|
||||
method: 'POST',
|
||||
body: { i: 'TOKEN', token: 'aaa', password: 'aaa' }
|
||||
});
|
||||
});
|
||||
|
||||
test('インスタンスの credential が指定されていても引数で credential が null ならば null としてリクエストされる', async () => {
|
||||
fetchMock.resetMocks();
|
||||
fetchMock.mockResponse(async (req) => {
|
||||
const body = await req.json();
|
||||
if (req.method == 'POST' && req.url == 'https://foundkey.test/api/i') {
|
||||
if (typeof body.i === 'string') {
|
||||
return JSON.stringify({ id: 'foo' });
|
||||
} else {
|
||||
return {
|
||||
status: 401,
|
||||
body: JSON.stringify({
|
||||
error: {
|
||||
message: 'Credential required.',
|
||||
code: 'CREDENTIAL_REQUIRED',
|
||||
id: '1384574d-a912-4b81-8601-c7b1c4085df1',
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return { status: 404 };
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const cli = new APIClient({
|
||||
origin: 'https://foundkey.test',
|
||||
credential: 'TOKEN',
|
||||
});
|
||||
|
||||
await cli.request('i', {}, null);
|
||||
} catch (e) {
|
||||
expect(isAPIError(e)).toEqual(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('api error', async () => {
|
||||
fetchMock.resetMocks();
|
||||
fetchMock.mockResponse(async (req) => {
|
||||
return {
|
||||
status: 500,
|
||||
body: JSON.stringify({
|
||||
error: {
|
||||
message: 'Internal error occurred. Please contact us if the error persists.',
|
||||
code: 'INTERNAL_ERROR',
|
||||
id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac',
|
||||
kind: 'server',
|
||||
},
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
const cli = new APIClient({
|
||||
origin: 'https://foundkey.test',
|
||||
credential: 'TOKEN',
|
||||
});
|
||||
|
||||
await cli.request('i');
|
||||
} catch (e: any) {
|
||||
expect(isAPIError(e)).toEqual(true);
|
||||
expect(e.id).toEqual('5d37dbcb-891e-41ca-a3d6-e690c97775ac');
|
||||
}
|
||||
});
|
||||
|
||||
test('network error', async () => {
|
||||
fetchMock.resetMocks();
|
||||
fetchMock.mockAbort();
|
||||
|
||||
try {
|
||||
const cli = new APIClient({
|
||||
origin: 'https://foundkey.test',
|
||||
credential: 'TOKEN',
|
||||
});
|
||||
|
||||
await cli.request('i');
|
||||
} catch (e) {
|
||||
expect(isAPIError(e)).toEqual(false);
|
||||
}
|
||||
});
|
||||
|
||||
test('json parse error', async () => {
|
||||
fetchMock.resetMocks();
|
||||
fetchMock.mockResponse(async (req) => {
|
||||
return {
|
||||
status: 500,
|
||||
body: '<html>I AM NOT JSON</html>'
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
const cli = new APIClient({
|
||||
origin: 'https://foundkey.test',
|
||||
credential: 'TOKEN',
|
||||
});
|
||||
|
||||
await cli.request('i');
|
||||
} catch (e) {
|
||||
expect(isAPIError(e)).toEqual(false);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,165 +0,0 @@
|
|||
import WS from 'jest-websocket-mock';
|
||||
import Stream from '../src/streaming';
|
||||
|
||||
describe('Streaming', () => {
|
||||
test('useChannel', async () => {
|
||||
const server = new WS('wss://foundkey.test/streaming');
|
||||
const stream = new Stream('https://foundkey.test', { token: 'TOKEN' });
|
||||
const mainChannelReceived: any[] = [];
|
||||
const main = stream.useChannel('main');
|
||||
main.on('meUpdated', payload => {
|
||||
mainChannelReceived.push(payload);
|
||||
});
|
||||
|
||||
const ws = await server.connected;
|
||||
expect(new URLSearchParams(new URL(ws.url).search).get('i')).toEqual('TOKEN');
|
||||
|
||||
const msg = JSON.parse(await server.nextMessage as string);
|
||||
const mainChannelId = msg.body.id;
|
||||
expect(msg.type).toEqual('connect');
|
||||
expect(msg.body.channel).toEqual('main');
|
||||
expect(mainChannelId != null).toEqual(true);
|
||||
|
||||
server.send(JSON.stringify({
|
||||
type: 'channel',
|
||||
body: {
|
||||
id: mainChannelId,
|
||||
type: 'meUpdated',
|
||||
body: {
|
||||
id: 'foo'
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
expect(mainChannelReceived[0]).toEqual({
|
||||
id: 'foo'
|
||||
});
|
||||
|
||||
stream.close();
|
||||
server.close();
|
||||
});
|
||||
|
||||
test('useChannel with parameters', async () => {
|
||||
const server = new WS('wss://foundkey.test/streaming');
|
||||
const stream = new Stream('https://foundkey.test', { token: 'TOKEN' });
|
||||
const messagingChannelReceived: any[] = [];
|
||||
const messaging = stream.useChannel('messaging', { otherparty: 'aaa' });
|
||||
messaging.on('message', payload => {
|
||||
messagingChannelReceived.push(payload);
|
||||
});
|
||||
|
||||
const ws = await server.connected;
|
||||
expect(new URLSearchParams(new URL(ws.url).search).get('i')).toEqual('TOKEN');
|
||||
|
||||
const msg = JSON.parse(await server.nextMessage as string);
|
||||
const messagingChannelId = msg.body.id;
|
||||
expect(msg.type).toEqual('connect');
|
||||
expect(msg.body.channel).toEqual('messaging');
|
||||
expect(msg.body.params).toEqual({ otherparty: 'aaa' });
|
||||
expect(messagingChannelId != null).toEqual(true);
|
||||
|
||||
server.send(JSON.stringify({
|
||||
type: 'channel',
|
||||
body: {
|
||||
id: messagingChannelId,
|
||||
type: 'message',
|
||||
body: {
|
||||
id: 'foo'
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
expect(messagingChannelReceived[0]).toEqual({
|
||||
id: 'foo'
|
||||
});
|
||||
|
||||
stream.close();
|
||||
server.close();
|
||||
});
|
||||
|
||||
test('ちゃんとチャンネルごとにidが異なる', async () => {
|
||||
const server = new WS('wss://foundkey.test/streaming');
|
||||
const stream = new Stream('https://foundkey.test', { token: 'TOKEN' });
|
||||
|
||||
stream.useChannel('messaging', { otherparty: 'aaa' });
|
||||
stream.useChannel('messaging', { otherparty: 'bbb' });
|
||||
|
||||
const ws = await server.connected;
|
||||
expect(new URLSearchParams(new URL(ws.url).search).get('i')).toEqual('TOKEN');
|
||||
|
||||
const msg = JSON.parse(await server.nextMessage as string);
|
||||
const messagingChannelId = msg.body.id;
|
||||
const msg2 = JSON.parse(await server.nextMessage as string);
|
||||
const messagingChannelId2 = msg2.body.id;
|
||||
|
||||
expect(messagingChannelId != null).toEqual(true);
|
||||
expect(messagingChannelId2 != null).toEqual(true);
|
||||
expect(messagingChannelId).not.toEqual(messagingChannelId2);
|
||||
|
||||
stream.close();
|
||||
server.close();
|
||||
});
|
||||
|
||||
test('Connection#send', async () => {
|
||||
const server = new WS('wss://foundkey.test/streaming');
|
||||
const stream = new Stream('https://foundkey.test', { token: 'TOKEN' });
|
||||
|
||||
const messaging = stream.useChannel('messaging', { otherparty: 'aaa' });
|
||||
messaging.send('read', { id: 'aaa' });
|
||||
|
||||
const ws = await server.connected;
|
||||
expect(new URLSearchParams(new URL(ws.url).search).get('i')).toEqual('TOKEN');
|
||||
|
||||
const connectMsg = JSON.parse(await server.nextMessage as string);
|
||||
const channelId = connectMsg.body.id;
|
||||
const msg = JSON.parse(await server.nextMessage as string);
|
||||
|
||||
expect(msg.type).toEqual('ch');
|
||||
expect(msg.body.id).toEqual(channelId);
|
||||
expect(msg.body.type).toEqual('read');
|
||||
expect(msg.body.body).toEqual({ id: 'aaa' });
|
||||
|
||||
stream.close();
|
||||
server.close();
|
||||
});
|
||||
|
||||
test('Connection#dispose', async () => {
|
||||
const server = new WS('wss://foundkey.test/streaming');
|
||||
const stream = new Stream('https://foundkey.test', { token: 'TOKEN' });
|
||||
const mainChannelReceived: any[] = [];
|
||||
const main = stream.useChannel('main');
|
||||
main.on('meUpdated', payload => {
|
||||
mainChannelReceived.push(payload);
|
||||
});
|
||||
|
||||
const ws = await server.connected;
|
||||
expect(new URLSearchParams(new URL(ws.url).search).get('i')).toEqual('TOKEN');
|
||||
|
||||
const msg = JSON.parse(await server.nextMessage as string);
|
||||
const mainChannelId = msg.body.id;
|
||||
expect(msg.type).toEqual('connect');
|
||||
expect(msg.body.channel).toEqual('main');
|
||||
expect(mainChannelId != null).toEqual(true);
|
||||
main.dispose();
|
||||
|
||||
server.send(JSON.stringify({
|
||||
type: 'channel',
|
||||
body: {
|
||||
id: mainChannelId,
|
||||
type: 'meUpdated',
|
||||
body: {
|
||||
id: 'foo'
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
expect(mainChannelReceived.length).toEqual(0);
|
||||
|
||||
stream.close();
|
||||
server.close();
|
||||
});
|
||||
|
||||
// TODO: SharedConnection#dispose して一定時間経ったら disconnect メッセージがサーバーに送られてくるかのテスト
|
||||
|
||||
// TODO: チャンネル接続が使いまわされるかのテスト
|
||||
});
|
|
@ -3,7 +3,7 @@ const path = require("path");
|
|||
|
||||
const ruleFiles = fs
|
||||
.readdirSync(__dirname)
|
||||
.filter((file) => file !== "index.js" && !file.endsWith("test.js"));
|
||||
.filter((file) => file !== "index.js");
|
||||
|
||||
const rules = Object.fromEntries(
|
||||
ruleFiles.map((file) => [path.basename(file, ".js"), require("./" + file)])
|
||||
|
|