diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2019-04-07 21:50:36 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-04-07 21:50:36 +0900 |
| commit | f0a29721c9fb10f97faf386bc9d6b1b2fad97895 (patch) | |
| tree | b5c1d38d698589bb444c0881a431391db91eb5bc /test | |
| parent | Update README.md [AUTOGEN] (#4639) (diff) | |
| download | sharkey-f0a29721c9fb10f97faf386bc9d6b1b2fad97895.tar.gz sharkey-f0a29721c9fb10f97faf386bc9d6b1b2fad97895.tar.bz2 sharkey-f0a29721c9fb10f97faf386bc9d6b1b2fad97895.zip | |
Use PostgreSQL instead of MongoDB (#4572)
* wip
* Update note.ts
* Update timeline.ts
* Update core.ts
* wip
* Update generate-visibility-query.ts
* wip
* wip
* wip
* wip
* wip
* Update global-timeline.ts
* wip
* wip
* wip
* Update vote.ts
* wip
* wip
* Update create.ts
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Update files.ts
* wip
* wip
* Update CONTRIBUTING.md
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Update read-notification.ts
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Update cancel.ts
* wip
* wip
* wip
* Update show.ts
* wip
* wip
* Update gen-id.ts
* Update create.ts
* Update id.ts
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Docker: Update files about Docker (#4599)
* Docker: Use cache if files used by `yarn install` was not updated
This patch reduces the number of times to installing node_modules.
For example, `yarn install` step will be skipped when only ".config/default.yml" is updated.
* Docker: Migrate MongoDB to Postgresql
Misskey uses Postgresql as a database instead of Mongodb since version 11.
* Docker: Uncomment about data persistence
This patch will save a lot of databases.
* wip
* wip
* wip
* Update activitypub.ts
* wip
* wip
* wip
* Update logs.ts
* wip
* Update drive-file.ts
* Update register.ts
* wip
* wip
* Update mentions.ts
* wip
* wip
* wip
* Update recommendation.ts
* wip
* Update index.ts
* wip
* Update recommendation.ts
* Doc: Update docker.ja.md and docker.en.md (#1) (#4608)
Update how to set up misskey.
* wip
* :v:
* wip
* Update note.ts
* Update postgre.ts
* wip
* wip
* wip
* wip
* Update add-file.ts
* wip
* wip
* wip
* Clean up
* Update logs.ts
* wip
* :pizza:
* wip
* Ad notes
* wip
* Update api-visibility.ts
* Update note.ts
* Update add-file.ts
* tests
* tests
* Update postgre.ts
* Update utils.ts
* wip
* wip
* Refactor
* wip
* Refactor
* wip
* wip
* Update show-users.ts
* Update update-instance.ts
* wip
* Update feed.ts
* Update outbox.ts
* Update outbox.ts
* Update user.ts
* wip
* Update list.ts
* Update update-hashtag.ts
* wip
* Update update-hashtag.ts
* Refactor
* Update update.ts
* wip
* wip
* :v:
* clean up
* docs
* Update push.ts
* wip
* Update api.ts
* wip
* :v:
* Update make-pagination-query.ts
* :v:
* Delete hashtags.ts
* Update instances.ts
* Update instances.ts
* Update create.ts
* Update search.ts
* Update reversi-game.ts
* Update signup.ts
* Update user.ts
* id
* Update example.yml
* :art:
* objectid
* fix
* reversi
* reversi
* Fix bug of chart engine
* Add test of chart engine
* Improve test
* Better testing
* Improve chart engine
* Refactor
* Add test of chart engine
* Refactor
* Add chart test
* Fix bug
* コミットし忘れ
* Refactoring
* :v:
* Add tests
* Add test
* Extarct note tests
* Refactor
* 存在しないユーザーにメンションできなくなっていた問題を修正
* Fix bug
* Update update-meta.ts
* Fix bug
* Update mention.vue
* Fix bug
* Update meta.ts
* Update CONTRIBUTING.md
* Fix bug
* Fix bug
* Fix bug
* Clean up
* Clean up
* Update notification.ts
* Clean up
* Add mute tests
* Add test
* Refactor
* Add test
* Fix test
* Refactor
* Refactor
* Add tests
* Update utils.ts
* Update utils.ts
* Fix test
* Update package.json
* Update update.ts
* Update manifest.ts
* Fix bug
* Fix bug
* Add test
* :art:
* Update endpoint permissions
* Updaye permisison
* Update person.ts
#4299
* データベースと同期しないように
* Fix bug
* Fix bug
* Update reversi-game.ts
* Use a feature of Node v11.7.0 to extract a public key (#4644)
* wip
* wip
* :v:
* Refactoring
#1540
* test
* test
* test
* test
* test
* test
* test
* Fix bug
* Fix test
* :sushi:
* wip
* #4471
* Add test for #4335
* Refactor
* Fix test
* Add tests
* :clock4:
* Fix bug
* Add test
* Add test
* rename
* Fix bug
Diffstat (limited to 'test')
| -rw-r--r-- | test/api-visibility.ts | 271 | ||||
| -rw-r--r-- | test/api.ts | 635 | ||||
| -rw-r--r-- | test/chart.ts | 323 | ||||
| -rw-r--r-- | test/mfm.ts | 4 | ||||
| -rw-r--r-- | test/mocha.opts | 3 | ||||
| -rw-r--r-- | test/mute.ts | 170 | ||||
| -rw-r--r-- | test/note.ts | 361 | ||||
| -rw-r--r-- | test/reaction-lib.ts | 4 | ||||
| -rw-r--r-- | test/streaming.ts | 913 | ||||
| -rw-r--r-- | test/user-notes.ts | 86 | ||||
| -rw-r--r-- | test/utils.ts | 99 |
11 files changed, 2051 insertions, 818 deletions
diff --git a/test/api-visibility.ts b/test/api-visibility.ts index 8380d54f1d..894d0d0753 100644 --- a/test/api-visibility.ts +++ b/test/api-visibility.ts @@ -6,40 +6,33 @@ * * To specify test: * > mocha test/api-visibility.ts --require ts-node/register -g 'test name' + * + * If the tests not start, try set following enviroment variables: + * TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true + * for more details, please see: https://github.com/TypeStrong/ts-node/issues/754 */ -import * as http from 'http'; -import * as assert from 'chai'; -import { async, _signup, _request, _uploadFile, _post, _react, resetDb } from './utils'; - -const expect = assert.expect; - -//#region process -Error.stackTraceLimit = Infinity; -// During the test the env variable is set to test process.env.NODE_ENV = 'test'; -// Display detail of unhandled promise rejection -process.on('unhandledRejection', console.dir); -//#endregion - -const app = require('../built/server/api').default; -const db = require('../built/db/mongodb').default; - -const server = http.createServer(app.callback()); - -//#region Utilities -const request = _request(server); -const signup = _signup(request); -const post = _post(request); -//#endregion +import * as assert from 'assert'; +import * as childProcess from 'child_process'; +import { async, signup, request, post } from './utils'; describe('API visibility', () => { - // Reset database each test - before(resetDb(db)); + let p: childProcess.ChildProcess; + + before(done => { + p = childProcess.spawn('node', [__dirname + '/../index.js'], { + stdio: ['inherit', 'inherit', 'ipc'], + env: { NODE_ENV: 'test' } + }); + p.on('message', message => { + if (message === 'ok') done(); + }); + }); after(() => { - server.close(); + p.kill(); }); describe('Note visibility', async () => { @@ -61,8 +54,6 @@ describe('API visibility', () => { let fol: any; /** specified-post */ let spe: any; - /** private-post */ - let pri: any; /** public-reply to target's post */ let pubR: any; @@ -72,8 +63,6 @@ describe('API visibility', () => { let folR: any; /** specified-reply to target's post */ let speR: any; - /** private-reply to target's post */ - let priR: any; /** public-mention to target */ let pubM: any; @@ -83,8 +72,6 @@ describe('API visibility', () => { let folM: any; /** specified-mention to target */ let speM: any; - /** private-mention to target */ - let priM: any; /** reply target post */ let tgt: any; @@ -112,7 +99,6 @@ describe('API visibility', () => { 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] }); - pri = await post(alice, { text: 'x', visibility: 'private' }); // replies tgt = await post(target, { text: 'y', visibility: 'public' }); @@ -120,14 +106,12 @@ describe('API visibility', () => { 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' }); - priR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'private' }); // 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: '@target x', replyId: tgt.id, visibility: 'specified' }); - priM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'private' }); //#endregion }); @@ -135,111 +119,90 @@ describe('API visibility', () => { // public it('[show] public-postを自分が見れる', async(async () => { const res = await show(pub.id, alice); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] public-postをフォロワーが見れる', async(async () => { const res = await show(pub.id, follower); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] public-postを非フォロワーが見れる', async(async () => { const res = await show(pub.id, other); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] public-postを未認証が見れる', async(async () => { const res = await show(pub.id, null); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); // home it('[show] home-postを自分が見れる', async(async () => { const res = await show(home.id, alice); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] home-postをフォロワーが見れる', async(async () => { const res = await show(home.id, follower); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] home-postを非フォロワーが見れる', async(async () => { const res = await show(home.id, other); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] home-postを未認証が見れる', async(async () => { const res = await show(home.id, null); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); // followers it('[show] followers-postを自分が見れる', async(async () => { const res = await show(fol.id, alice); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] followers-postをフォロワーが見れる', async(async () => { const res = await show(fol.id, follower); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] followers-postを非フォロワーが見れない', async(async () => { const res = await show(fol.id, other); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); it('[show] followers-postを未認証が見れない', async(async () => { const res = await show(fol.id, null); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); // specified it('[show] specified-postを自分が見れる', async(async () => { const res = await show(spe.id, alice); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] specified-postを指定ユーザーが見れる', async(async () => { const res = await show(spe.id, target); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] specified-postをフォロワーが見れない', async(async () => { const res = await show(spe.id, follower); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); it('[show] specified-postを非フォロワーが見れない', async(async () => { const res = await show(spe.id, other); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); it('[show] specified-postを未認証が見れない', async(async () => { const res = await show(spe.id, null); - expect(res.body).have.property('isHidden').eql(true); - })); - - // private - it('[show] private-postを自分が見れる', async(async () => { - const res = await show(pri.id, alice); - expect(res.body).have.property('text').eql('x'); - })); - - it('[show] private-postをフォロワーが見れない', async(async () => { - const res = await show(pri.id, follower); - expect(res.body).have.property('isHidden').eql(true); - })); - - it('[show] private-postを非フォロワーが見れない', async(async () => { - const res = await show(pri.id, other); - expect(res.body).have.property('isHidden').eql(true); - })); - - it('[show] private-postを未認証が見れない', async(async () => { - const res = await show(pri.id, null); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); //#endregion @@ -247,131 +210,110 @@ describe('API visibility', () => { // public it('[show] public-replyを自分が見れる', async(async () => { const res = await show(pubR.id, alice); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] public-replyをされた人が見れる', async(async () => { const res = await show(pubR.id, target); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] public-replyをフォロワーが見れる', async(async () => { const res = await show(pubR.id, follower); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] public-replyを非フォロワーが見れる', async(async () => { const res = await show(pubR.id, other); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] public-replyを未認証が見れる', async(async () => { const res = await show(pubR.id, null); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); // home it('[show] home-replyを自分が見れる', async(async () => { const res = await show(homeR.id, alice); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] home-replyをされた人が見れる', async(async () => { const res = await show(homeR.id, target); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] home-replyをフォロワーが見れる', async(async () => { const res = await show(homeR.id, follower); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] home-replyを非フォロワーが見れる', async(async () => { const res = await show(homeR.id, other); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] home-replyを未認証が見れる', async(async () => { const res = await show(homeR.id, null); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); // followers it('[show] followers-replyを自分が見れる', async(async () => { const res = await show(folR.id, alice); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] followers-replyを非フォロワーでもリプライされていれば見れる', async(async () => { const res = await show(folR.id, target); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] followers-replyをフォロワーが見れる', async(async () => { const res = await show(folR.id, follower); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] followers-replyを非フォロワーが見れない', async(async () => { const res = await show(folR.id, other); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); it('[show] followers-replyを未認証が見れない', async(async () => { const res = await show(folR.id, null); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); // specified it('[show] specified-replyを自分が見れる', async(async () => { const res = await show(speR.id, alice); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] specified-replyを指定ユーザーが見れる', async(async () => { const res = await show(speR.id, target); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] specified-replyをされた人が指定されてなくても見れる', async(async () => { const res = await show(speR.id, target); - expect(res.body).have.property('text').eql('x'); + assert.strictEqual(res.body.text, 'x'); })); it('[show] specified-replyをフォロワーが見れない', async(async () => { const res = await show(speR.id, follower); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); it('[show] specified-replyを非フォロワーが見れない', async(async () => { const res = await show(speR.id, other); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); it('[show] specified-replyを未認証が見れない', async(async () => { const res = await show(speR.id, null); - expect(res.body).have.property('isHidden').eql(true); - })); - - // private - it('[show] private-replyを自分が見れる', async(async () => { - const res = await show(priR.id, alice); - expect(res.body).have.property('text').eql('x'); - })); - - it('[show] private-replyをフォロワーが見れない', async(async () => { - const res = await show(priR.id, follower); - expect(res.body).have.property('isHidden').eql(true); - })); - - it('[show] private-replyを非フォロワーが見れない', async(async () => { - const res = await show(priR.id, other); - expect(res.body).have.property('isHidden').eql(true); - })); - - it('[show] private-replyを未認証が見れない', async(async () => { - const res = await show(priR.id, null); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); //#endregion @@ -379,193 +321,172 @@ describe('API visibility', () => { // public it('[show] public-mentionを自分が見れる', async(async () => { const res = await show(pubM.id, alice); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); it('[show] public-mentionをされた人が見れる', async(async () => { const res = await show(pubM.id, target); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); it('[show] public-mentionをフォロワーが見れる', async(async () => { const res = await show(pubM.id, follower); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); it('[show] public-mentionを非フォロワーが見れる', async(async () => { const res = await show(pubM.id, other); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); it('[show] public-mentionを未認証が見れる', async(async () => { const res = await show(pubM.id, null); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); // home it('[show] home-mentionを自分が見れる', async(async () => { const res = await show(homeM.id, alice); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); it('[show] home-mentionをされた人が見れる', async(async () => { const res = await show(homeM.id, target); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); it('[show] home-mentionをフォロワーが見れる', async(async () => { const res = await show(homeM.id, follower); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); it('[show] home-mentionを非フォロワーが見れる', async(async () => { const res = await show(homeM.id, other); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); it('[show] home-mentionを未認証が見れる', async(async () => { const res = await show(homeM.id, null); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); // followers it('[show] followers-mentionを自分が見れる', async(async () => { const res = await show(folM.id, alice); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); - it('[show] followers-mentionを非フォロワーでもメンションされていれば見れる', async(async () => { + it('[show] followers-mentionを非フォロワーがメンションされていても見れない', async(async () => { const res = await show(folM.id, target); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.isHidden, true); })); it('[show] followers-mentionをフォロワーが見れる', async(async () => { const res = await show(folM.id, follower); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); it('[show] followers-mentionを非フォロワーが見れない', async(async () => { const res = await show(folM.id, other); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); it('[show] followers-mentionを未認証が見れない', async(async () => { const res = await show(folM.id, null); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); // specified it('[show] specified-mentionを自分が見れる', async(async () => { const res = await show(speM.id, alice); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); it('[show] specified-mentionを指定ユーザーが見れる', async(async () => { const res = await show(speM.id, target); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.text, '@target x'); })); - it('[show] specified-mentionをされた人が指定されてなくても見れる', async(async () => { + it('[show] specified-mentionをされた人が指定されてなかったら見れない', async(async () => { const res = await show(speM.id, target); - expect(res.body).have.property('text').eql('@target x'); + assert.strictEqual(res.body.isHidden, true); })); it('[show] specified-mentionをフォロワーが見れない', async(async () => { const res = await show(speM.id, follower); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); it('[show] specified-mentionを非フォロワーが見れない', async(async () => { const res = await show(speM.id, other); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); it('[show] specified-mentionを未認証が見れない', async(async () => { const res = await show(speM.id, null); - expect(res.body).have.property('isHidden').eql(true); - })); - - // private - it('[show] private-mentionを自分が見れる', async(async () => { - const res = await show(priM.id, alice); - expect(res.body).have.property('text').eql('@target x'); - })); - - it('[show] private-mentionをフォロワーが見れない', async(async () => { - const res = await show(priM.id, follower); - expect(res.body).have.property('isHidden').eql(true); - })); - - it('[show] private-mentionを非フォロワーが見れない', async(async () => { - const res = await show(priM.id, other); - expect(res.body).have.property('isHidden').eql(true); - })); - - it('[show] private-mentionを未認証が見れない', async(async () => { - const res = await show(priM.id, null); - expect(res.body).have.property('isHidden').eql(true); + assert.strictEqual(res.body.isHidden, true); })); //#endregion //#region HTL it('[HTL] public-post が 自分が見れる', async(async () => { const res = await request('/notes/timeline', { limit: 100 }, alice); - expect(res).have.status(200); + assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id == pub.id); - expect(notes[0]).have.property('text').eql('x'); + assert.strictEqual(notes[0].text, 'x'); })); it('[HTL] public-post が 非フォロワーから見れない', async(async () => { const res = await request('/notes/timeline', { limit: 100 }, other); - expect(res).have.status(200); + assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id == pub.id); - expect(notes).length(0); + assert.strictEqual(notes.length, 0); })); it('[HTL] followers-post が フォロワーから見れる', async(async () => { const res = await request('/notes/timeline', { limit: 100 }, follower); - expect(res).have.status(200); + assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id == fol.id); - expect(notes[0]).have.property('text').eql('x'); + assert.strictEqual(notes[0].text, 'x'); })); //#endregion //#region RTL it('[replies] followers-reply が フォロワーから見れる', async(async () => { const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, follower); - expect(res).have.status(200); + assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id == folR.id); - expect(notes[0]).have.property('text').eql('x'); + assert.strictEqual(notes[0].text, 'x'); })); it('[replies] followers-reply が 非フォロワー (リプライ先ではない) から見れない', async(async () => { const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, other); - expect(res).have.status(200); + assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id == folR.id); - expect(notes).length(0); + assert.strictEqual(notes.length, 0); })); it('[replies] followers-reply が 非フォロワー (リプライ先である) から見れる', async(async () => { const res = await request('/notes/replies', { noteId: tgt.id, limit: 100 }, target); - expect(res).have.status(200); + assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id == folR.id); - expect(notes[0]).have.property('text').eql('x'); + assert.strictEqual(notes[0].text, 'x'); })); //#endregion //#region MTL it('[mentions] followers-reply が 非フォロワー (リプライ先である) から見れる', async(async () => { const res = await request('/notes/mentions', { limit: 100 }, target); - expect(res).have.status(200); + assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id == folR.id); - expect(notes[0]).have.property('text').eql('x'); + assert.strictEqual(notes[0].text, 'x'); })); it('[mentions] followers-mention が 非フォロワー (メンション先である) から見れる', async(async () => { const res = await request('/notes/mentions', { limit: 100 }, target); - expect(res).have.status(200); + assert.strictEqual(res.status, 200); const notes = res.body.filter((n: any) => n.id == folM.id); - expect(notes[0]).have.property('text').eql('@target x'); + assert.strictEqual(notes[0].text, '@target x'); })); //#endregion }); diff --git a/test/api.ts b/test/api.ts index cc4521d3dc..71443c5730 100644 --- a/test/api.ts +++ b/test/api.ts @@ -6,44 +6,35 @@ * * To specify test: * > mocha test/api.ts --require ts-node/register -g 'test name' + * + * If the tests not start, try set following enviroment variables: + * TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true + * for more details, please see: https://github.com/TypeStrong/ts-node/issues/754 */ -import * as http from 'http'; -import * as fs from 'fs'; -import * as assert from 'chai'; -import { async, _signup, _request, _uploadFile, _post, _react, resetDb } from './utils'; - -const expect = assert.expect; - -//#region process -Error.stackTraceLimit = Infinity; - -// During the test the env variable is set to test process.env.NODE_ENV = 'test'; -// Display detail of unhandled promise rejection -process.on('unhandledRejection', console.dir); -//#endregion - -const app = require('../built/server/api').default; -const db = require('../built/db/mongodb').default; - -const server = http.createServer(app.callback()); - -//#region Utilities -const request = _request(server); -const signup = _signup(request); -const post = _post(request); -const react = _react(request); -const uploadFile = _uploadFile(server); -//#endregion +import * as assert from 'assert'; +import * as childProcess from 'child_process'; +import { async, signup, request, post, react, uploadFile } from './utils'; describe('API', () => { - // Reset database each test - beforeEach(resetDb(db)); + let p: childProcess.ChildProcess; - after(() => { - server.close(); + beforeEach(done => { + p = childProcess.spawn('node', [__dirname + '/../index.js'], { + stdio: ['inherit', 'inherit', 'ipc'], + env: { NODE_ENV: 'test' } + }); + p.on('message', message => { + if (message === 'ok') { + done(); + } + }); + }); + + afterEach(() => { + p.kill(); }); describe('signup', () => { @@ -52,7 +43,7 @@ describe('API', () => { username: 'test.', password: 'test' }); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('空のパスワードでアカウントが作成できない', async(async () => { @@ -60,7 +51,7 @@ describe('API', () => { username: 'test', password: '' }); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('正しくアカウントが作成できる', async(async () => { @@ -71,9 +62,9 @@ describe('API', () => { const res = await request('/signup', me); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('username').eql(me.username); + 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 () => { @@ -86,7 +77,7 @@ describe('API', () => { password: 'test' }); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); }); @@ -102,7 +93,7 @@ describe('API', () => { password: 'bar' }); - expect(res).have.status(403); + assert.strictEqual(res.status, 403); })); it('クエリをインジェクションできない', async(async () => { @@ -117,7 +108,7 @@ describe('API', () => { } }); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('正しい情報でサインインできる', async(async () => { @@ -131,7 +122,7 @@ describe('API', () => { password: 'foo' }); - expect(res).have.status(200); + assert.strictEqual(res.status, 200); })); }); @@ -149,12 +140,11 @@ describe('API', () => { birthday: myBirthday }, me); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('name').eql(myName); - expect(res.body).have.nested.property('profile').a('object'); - expect(res.body).have.nested.property('profile.location').eql(myLocation); - expect(res.body).have.nested.property('profile.birthday').eql(myBirthday); + 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 () => { @@ -162,7 +152,7 @@ describe('API', () => { const res = await request('/i/update', { name: ' ' }, me); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('誕生日の設定を削除できる', async(async () => { @@ -175,10 +165,9 @@ describe('API', () => { birthday: null }, me); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.nested.property('profile').a('object'); - expect(res.body).have.nested.property('profile.birthday').eql(null); + 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 () => { @@ -186,7 +175,7 @@ describe('API', () => { const res = await request('/i/update', { birthday: '2000/09/07' }, me); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); }); @@ -198,365 +187,23 @@ describe('API', () => { userId: me.id }, me); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('id').eql(me.id); + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); + assert.strictEqual(res.body.id, me.id); })); it('ユーザーが存在しなかったら怒る', async(async () => { const res = await request('/users/show', { userId: '000000000000000000000000' }); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('間違ったIDで怒られる', async(async () => { const res = await request('/users/show', { userId: 'kyoppie' }); - expect(res).have.status(400); - })); - }); - - describe('notes/create', () => { - it('投稿できる', async(async () => { - const me = await signup(); - const post = { - text: 'test' - }; - - const res = await request('/notes/create', post, me); - - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('createdNote'); - expect(res.body.createdNote).have.property('text').eql(post.text); - })); - - it('ファイルを添付できる', async(async () => { - const me = await signup(); - const file = await uploadFile(me); - - const res = await request('/notes/create', { - fileIds: [file.id] - }, me); - - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('createdNote'); - expect(res.body.createdNote).have.property('fileIds').eql([file.id]); - })); - - it('他人のファイルは無視', async(async () => { - const me = await signup({ username: 'alice' }); - const bob = await signup({ username: 'bob' }); - const file = await uploadFile(bob); - - const res = await request('/notes/create', { - text: 'test', - fileIds: [file.id] - }, me); - - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('createdNote'); - expect(res.body.createdNote).have.property('fileIds').eql([]); - })); - - it('存在しないファイルは無視', async(async () => { - const me = await signup(); - - const res = await request('/notes/create', { - text: 'test', - fileIds: ['000000000000000000000000'] - }, me); - - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('createdNote'); - expect(res.body.createdNote).have.property('fileIds').eql([]); - })); - - it('不正なファイルIDで怒られる', async(async () => { - const me = await signup(); - const res = await request('/notes/create', { - fileIds: ['kyoppie'] - }, me); - expect(res).have.status(400); - })); - - it('返信できる', async(async () => { - const bob = await signup({ username: 'bob' }); - const bobPost = await post(bob); - - const alice = await signup({ username: 'alice' }); - const alicePost = { - text: 'test', - replyId: bobPost.id - }; - - const res = await request('/notes/create', alicePost, alice); - - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('createdNote'); - expect(res.body.createdNote).have.property('text').eql(alicePost.text); - expect(res.body.createdNote).have.property('replyId').eql(alicePost.replyId); - expect(res.body.createdNote).have.property('reply'); - expect(res.body.createdNote.reply).have.property('text').eql(alicePost.text); - })); - - it('renoteできる', async(async () => { - const bob = await signup({ username: 'bob' }); - const bobPost = await post(bob, { - text: 'test' - }); - - const alice = await signup({ username: 'alice' }); - const alicePost = { - renoteId: bobPost.id - }; - - const res = await request('/notes/create', alicePost, alice); - - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('createdNote'); - expect(res.body.createdNote).have.property('renoteId').eql(alicePost.renoteId); - expect(res.body.createdNote).have.property('renote'); - expect(res.body.createdNote.renote).have.property('text').eql(bobPost.text); - })); - - it('引用renoteできる', async(async () => { - const bob = await signup({ username: 'bob' }); - const bobPost = await post(bob, { - text: 'test' - }); - - const alice = await signup({ username: 'alice' }); - const alicePost = { - text: 'test', - renoteId: bobPost.id - }; - - const res = await request('/notes/create', alicePost, alice); - - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('createdNote'); - expect(res.body.createdNote).have.property('text').eql(alicePost.text); - expect(res.body.createdNote).have.property('renoteId').eql(alicePost.renoteId); - expect(res.body.createdNote).have.property('renote'); - expect(res.body.createdNote.renote).have.property('text').eql(bobPost.text); - })); - - it('文字数ぎりぎりで怒られない', async(async () => { - const me = await signup(); - const post = { - text: '!'.repeat(1000) - }; - const res = await request('/notes/create', post, me); - expect(res).have.status(200); - })); - - it('文字数オーバーで怒られる', async(async () => { - const me = await signup(); - const post = { - text: '!'.repeat(1001) - }; - const res = await request('/notes/create', post, me); - expect(res).have.status(400); - })); - - it('存在しないリプライ先で怒られる', async(async () => { - const me = await signup(); - const post = { - text: 'test', - replyId: '000000000000000000000000' - }; - const res = await request('/notes/create', post, me); - expect(res).have.status(400); - })); - - it('存在しないrenote対象で怒られる', async(async () => { - const me = await signup(); - const post = { - renoteId: '000000000000000000000000' - }; - const res = await request('/notes/create', post, me); - expect(res).have.status(400); - })); - - it('不正なリプライ先IDで怒られる', async(async () => { - const me = await signup(); - const post = { - text: 'test', - replyId: 'foo' - }; - const res = await request('/notes/create', post, me); - expect(res).have.status(400); - })); - - it('不正なrenote対象IDで怒られる', async(async () => { - const me = await signup(); - const post = { - renoteId: 'foo' - }; - const res = await request('/notes/create', post, me); - expect(res).have.status(400); - })); - - it('投票を添付できる', async(async () => { - const me = await signup(); - - const res = await request('/notes/create', { - text: 'test', - poll: { - choices: ['foo', 'bar'] - } - }, me); - - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('createdNote'); - expect(res.body.createdNote).have.property('poll'); - })); - - it('投票の選択肢が無くて怒られる', async(async () => { - const me = await signup(); - const res = await request('/notes/create', { - poll: {} - }, me); - expect(res).have.status(400); - })); - - it('投票の選択肢が無くて怒られる (空の配列)', async(async () => { - const me = await signup(); - const res = await request('/notes/create', { - poll: { - choices: [] - } - }, me); - expect(res).have.status(400); - })); - - it('投票の選択肢が1つで怒られる', async(async () => { - const me = await signup(); - const res = await request('/notes/create', { - poll: { - choices: ['Strawberry Pasta'] - } - }, me); - expect(res).have.status(400); - })); - - it('投票できる', async(async () => { - const me = await signup(); - - const { body } = await request('/notes/create', { - text: 'test', - poll: { - choices: ['sakura', 'izumi', 'ako'] - } - }, me); - - const res = await request('/notes/polls/vote', { - noteId: body.createdNote.id, - choice: 1 - }, me); - - expect(res).have.status(204); - })); - - it('複数投票できない', async(async () => { - const me = await signup(); - - const { body } = await request('/notes/create', { - text: 'test', - poll: { - choices: ['sakura', 'izumi', 'ako'] - } - }, me); - - await request('/notes/polls/vote', { - noteId: body.createdNote.id, - choice: 0 - }, me); - - const res = await request('/notes/polls/vote', { - noteId: body.createdNote.id, - choice: 2 - }, me); - - expect(res).have.status(400); - })); - - it('許可されている場合は複数投票できる', async(async () => { - const me = await signup(); - - const { body } = await request('/notes/create', { - text: 'test', - poll: { - choices: ['sakura', 'izumi', 'ako'], - multiple: true - } - }, me); - - await request('/notes/polls/vote', { - noteId: body.createdNote.id, - choice: 0 - }, me); - - await request('/notes/polls/vote', { - noteId: body.createdNote.id, - choice: 1 - }, me); - - const res = await request('/notes/polls/vote', { - noteId: body.createdNote.id, - choice: 2 - }, me); - - expect(res).have.status(204); - })); - - it('締め切られている場合は投票できない', async(async () => { - const me = await signup(); - - const { body } = await request('/notes/create', { - text: 'test', - poll: { - choices: ['sakura', 'izumi', 'ako'], - expiredAfter: 1 - } - }, me); - - await new Promise(x => setTimeout(x, 2)); - - const res = await request('/notes/polls/vote', { - noteId: body.createdNote.id, - choice: 1 - }, me); - - expect(res).have.status(400); - })); - - it('同じユーザーに複数メンションしても内部的にまとめられる', async(async () => { - const alice = await signup({ username: 'alice' }); - const bob = await signup({ username: 'bob' }); - const post = { - text: '@bob @bob @bob yo' - }; - - const res = await request('/notes/create', post, alice); - - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('createdNote'); - expect(res.body.createdNote).have.property('text').eql(post.text); - - const noteDoc = await db.get('notes').findOne({ _id: res.body.createdNote.id }); - expect(noteDoc.mentions.map((id: any) => id.toString())).eql([bob.id.toString()]); + assert.strictEqual(res.status, 400); })); }); @@ -571,24 +218,24 @@ describe('API', () => { noteId: myPost.id }, me); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('id').eql(myPost.id); - expect(res.body).have.property('text').eql(myPost.text); + 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' }); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('間違ったIDで怒られる', async(async () => { const res = await request('/notes/show', { noteId: 'kyoppie' }); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); }); @@ -603,7 +250,7 @@ describe('API', () => { reaction: 'like' }, alice); - expect(res).have.status(204); + assert.strictEqual(res.status, 204); })); it('自分の投稿にはリアクションできない', async(async () => { @@ -615,7 +262,7 @@ describe('API', () => { reaction: 'like' }, me); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('二重にリアクションできない', async(async () => { @@ -630,7 +277,7 @@ describe('API', () => { reaction: 'like' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('存在しない投稿にはリアクションできない', async(async () => { @@ -641,7 +288,7 @@ describe('API', () => { reaction: 'like' }, me); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('空のパラメータで怒られる', async(async () => { @@ -649,7 +296,7 @@ describe('API', () => { const res = await request('/notes/reactions/create', {}, me); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('間違ったIDで怒られる', async(async () => { @@ -660,7 +307,7 @@ describe('API', () => { reaction: 'like' }, me); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); }); @@ -673,7 +320,7 @@ describe('API', () => { userId: alice.id }, bob); - expect(res).have.status(200); + assert.strictEqual(res.status, 200); })); it('既にフォローしている場合は怒る', async(async () => { @@ -687,7 +334,7 @@ describe('API', () => { userId: alice.id }, bob); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('存在しないユーザーはフォローできない', async(async () => { @@ -697,7 +344,7 @@ describe('API', () => { userId: '000000000000000000000000' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('自分自身はフォローできない', async(async () => { @@ -707,7 +354,7 @@ describe('API', () => { userId: alice.id }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('空のパラメータで怒られる', async(async () => { @@ -715,7 +362,7 @@ describe('API', () => { const res = await request('/following/create', {}, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('間違ったIDで怒られる', async(async () => { @@ -725,7 +372,7 @@ describe('API', () => { userId: 'foo' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); }); @@ -741,7 +388,7 @@ describe('API', () => { userId: alice.id }, bob); - expect(res).have.status(200); + assert.strictEqual(res.status, 200); })); it('フォローしていない場合は怒る', async(async () => { @@ -752,7 +399,7 @@ describe('API', () => { userId: alice.id }, bob); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('存在しないユーザーはフォロー解除できない', async(async () => { @@ -762,7 +409,7 @@ describe('API', () => { userId: '000000000000000000000000' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('自分自身はフォロー解除できない', async(async () => { @@ -772,7 +419,7 @@ describe('API', () => { userId: alice.id }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('空のパラメータで怒られる', async(async () => { @@ -780,7 +427,7 @@ describe('API', () => { const res = await request('/following/delete', {}, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('間違ったIDで怒られる', async(async () => { @@ -790,7 +437,7 @@ describe('API', () => { userId: 'kyoppie' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); }); @@ -799,20 +446,20 @@ describe('API', () => { it('ドライブ情報を取得できる', async(async () => { const bob = await signup({ username: 'bob' }); await uploadFile({ - userId: me._id, - datasize: 256 + userId: me.id, + size: 256 }); await uploadFile({ - userId: me._id, - datasize: 512 + userId: me.id, + size: 512 }); await uploadFile({ - userId: me._id, - datasize: 1024 + userId: me.id, + size: 1024 }); const res = await request('/drive', {}, me); - expect(res).have.status(200); - expect(res.body).be.a('object'); + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); expect(res.body).have.property('usage').eql(1792); }));*/ }); @@ -821,14 +468,11 @@ describe('API', () => { it('ファイルを作成できる', async(async () => { const alice = await signup({ username: 'alice' }); - const res = await assert.request(server) - .post('/drive/files/create') - .field('i', alice.token) - .attach('file', fs.readFileSync(__dirname + '/resources/Lenna.png'), 'Lenna.png'); + const res = await uploadFile(alice); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('name').eql('Lenna.png'); + 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 () => { @@ -836,21 +480,18 @@ describe('API', () => { const res = await request('/drive/files/create', {}, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('SVGファイルを作成できる', async(async () => { const izumi = await signup({ username: 'izumi' }); - const res = await assert.request(server) - .post('/drive/files/create') - .field('i', izumi.token) - .attach('file', fs.readFileSync(__dirname + '/resources/image.svg'), 'image.svg'); + const res = await uploadFile(izumi, __dirname + '/resources/image.svg'); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('name').eql('image.svg'); - expect(res.body).have.property('type').eql('image/svg+xml'); + 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'); })); }); @@ -865,9 +506,9 @@ describe('API', () => { name: newName }, alice); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('name').eql(newName); + 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 () => { @@ -880,7 +521,7 @@ describe('API', () => { name: 'いちごパスタ.png' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('親フォルダを更新できる', async(async () => { @@ -895,9 +536,9 @@ describe('API', () => { folderId: folder.id }, alice); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('folderId').eql(folder.id); + 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 () => { @@ -918,9 +559,9 @@ describe('API', () => { folderId: null }, alice); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('folderId').eql(null); + 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 () => { @@ -936,7 +577,7 @@ describe('API', () => { folderId: folder.id }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('存在しないフォルダで怒られる', async(async () => { @@ -948,7 +589,7 @@ describe('API', () => { folderId: '000000000000000000000000' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('不正なフォルダIDで怒られる', async(async () => { @@ -960,7 +601,7 @@ describe('API', () => { folderId: 'foo' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('ファイルが存在しなかったら怒る', async(async () => { @@ -971,7 +612,7 @@ describe('API', () => { name: 'いちごパスタ.png' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('間違ったIDで怒られる', async(async () => { @@ -982,7 +623,7 @@ describe('API', () => { name: 'いちごパスタ.png' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); }); @@ -994,9 +635,9 @@ describe('API', () => { name: 'test' }, alice); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('name').eql('test'); + assert.strictEqual(res.status, 200); + assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true); + assert.strictEqual(res.body.name, 'test'); })); }); @@ -1012,9 +653,9 @@ describe('API', () => { name: 'new name' }, alice); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('name').eql('new name'); + 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 () => { @@ -1029,7 +670,7 @@ describe('API', () => { name: 'new name' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('親フォルダを更新できる', async(async () => { @@ -1046,9 +687,9 @@ describe('API', () => { parentId: parentFolder.id }, alice); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('parentId').eql(parentFolder.id); + 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 () => { @@ -1069,9 +710,9 @@ describe('API', () => { parentId: null }, alice); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('parentId').eql(null); + 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 () => { @@ -1089,7 +730,7 @@ describe('API', () => { parentId: parentFolder.id }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('フォルダが循環するような構造にできない', async(async () => { @@ -1110,7 +751,7 @@ describe('API', () => { parentId: parentFolder.id }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('フォルダが循環するような構造にできない(再帰的)', async(async () => { @@ -1138,7 +779,7 @@ describe('API', () => { parentId: folderC.id }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('フォルダが循環するような構造にできない(自身)', async(async () => { @@ -1166,7 +807,7 @@ describe('API', () => { parentId: '000000000000000000000000' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('不正な親フォルダIDで怒られる', async(async () => { @@ -1180,7 +821,7 @@ describe('API', () => { parentId: 'foo' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('存在しないフォルダを更新できない', async(async () => { @@ -1190,7 +831,7 @@ describe('API', () => { folderId: '000000000000000000000000' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('不正なフォルダIDで怒られる', async(async () => { @@ -1200,7 +841,7 @@ describe('API', () => { folderId: 'foo' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); }); @@ -1214,9 +855,9 @@ describe('API', () => { text: 'test' }, alice); - expect(res).have.status(200); - expect(res.body).be.a('object'); - expect(res.body).have.property('text').eql('test'); + 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 () => { @@ -1227,7 +868,7 @@ describe('API', () => { text: 'Yo' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('存在しないユーザーにはメッセージを送信できない', async(async () => { @@ -1238,7 +879,7 @@ describe('API', () => { text: 'test' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('不正なユーザーIDで怒られる', async(async () => { @@ -1249,7 +890,7 @@ describe('API', () => { text: 'test' }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('テキストが無くて怒られる', async(async () => { @@ -1260,7 +901,7 @@ describe('API', () => { userId: bob.id }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); it('文字数オーバーで怒られる', async(async () => { @@ -1272,7 +913,7 @@ describe('API', () => { text: '!'.repeat(1001) }, alice); - expect(res).have.status(400); + assert.strictEqual(res.status, 400); })); }); @@ -1297,9 +938,9 @@ describe('API', () => { noteId: alicePost.id }, carol); - expect(res).have.status(200); - expect(res.body).be.a('array'); - expect(res.body).length(0); + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + assert.strictEqual(res.body.length, 0); })); }); @@ -1319,10 +960,10 @@ describe('API', () => { const res = await request('/notes/timeline', {}, bob); - expect(res).have.status(200); - expect(res.body).be.a('array'); - expect(res.body).length(1); - expect(res.body[0].id).equals(alicePost.id); + 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); })); }); }); diff --git a/test/chart.ts b/test/chart.ts new file mode 100644 index 0000000000..b3976b03ba --- /dev/null +++ b/test/chart.ts @@ -0,0 +1,323 @@ +/* + * Tests of chart engine + * + * How to run the tests: + * > mocha test/chart.ts --require ts-node/register + * + * To specify test: + * > mocha test/chart.ts --require ts-node/register -g 'test name' + * + * If the tests not start, try set following enviroment variables: + * TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true + * for more details, please see: https://github.com/TypeStrong/ts-node/issues/754 + */ + +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import * as lolex from 'lolex'; +import { async } from './utils'; +import { getConnection, createConnection } from 'typeorm'; +const config = require('../built/config').default; +const Chart = require('../built/services/chart/core').default; +const _TestChart = require('../built/services/chart/charts/schemas/test'); +const _TestGroupedChart = require('../built/services/chart/charts/schemas/test-grouped'); +const _TestUniqueChart = require('../built/services/chart/charts/schemas/test-unique'); + +function initDb() { + try { + const conn = getConnection(); + return Promise.resolve(conn); + } catch (e) {} + + return createConnection({ + type: 'postgres', + host: config.db.host, + port: config.db.port, + username: config.db.user, + password: config.db.pass, + database: config.db.db, + synchronize: true, + dropSchema: true, + entities: [ + Chart.schemaToEntity(_TestChart.name, _TestChart.schema), + Chart.schemaToEntity(_TestGroupedChart.name, _TestGroupedChart.schema), + Chart.schemaToEntity(_TestUniqueChart.name, _TestUniqueChart.schema) + ] + }); +} + +describe('Chart', () => { + let testChart: any; + let testGroupedChart: any; + let testUniqueChart: any; + let connection: any; + let clock: lolex.InstalledClock<lolex.Clock>; + + before(done => { + initDb().then(c => { + connection = c; + done(); + }); + }); + + beforeEach(done => { + const TestChart = require('../built/services/chart/charts/classes/test').default; + testChart = new TestChart(); + + const TestGroupedChart = require('../built/services/chart/charts/classes/test-grouped').default; + testGroupedChart = new TestGroupedChart(); + + const TestUniqueChart = require('../built/services/chart/charts/classes/test-unique').default; + testUniqueChart = new TestUniqueChart(); + + clock = lolex.install({ + now: new Date('2000-01-01 00:00:00') + }); + + connection.synchronize().then(done); + }); + + afterEach(done => { + clock.uninstall(); + connection.dropDatabase().then(done); + }); + + it('Can updates', async(async () => { + await testChart.increment(); + + const chartHours = await testChart.getChart('hour', 3); + const chartDays = await testChart.getChart('day', 3); + + 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('Empty chart', async(async () => { + const chartHours = await testChart.getChart('hour', 3); + const chartDays = await testChart.getChart('day', 3); + + 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(async () => { + await testChart.increment(); + await testChart.increment(); + await testChart.increment(); + + const chartHours = await testChart.getChart('hour', 3); + const chartDays = await testChart.getChart('day', 3); + + 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('Can updates at different times', async(async () => { + await testChart.increment(); + + clock.tick('01:00:00'); + + await testChart.increment(); + + const chartHours = await testChart.getChart('hour', 3); + const chartDays = await testChart.getChart('day', 3); + + 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(async () => { + await testChart.increment(); + + clock.tick('02:00:00'); + + await testChart.increment(); + + const chartHours = await testChart.getChart('hour', 3); + const chartDays = await testChart.getChart('day', 3); + + 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(async () => { + await testChart.increment(); + + clock.tick('05:00:00'); + + const chartHours = await testChart.getChart('hour', 3); + const chartDays = await testChart.getChart('day', 3); + + 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(async () => { + await testChart.increment(); + clock.tick('05:00:00'); + await testChart.increment(); + + const chartHours = await testChart.getChart('hour', 3); + const chartDays = await testChart.getChart('day', 3); + + 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] + }, + }); + })); + + describe('Grouped', () => { + it('Can updates', async(async () => { + await testGroupedChart.increment('alice'); + + const aliceChartHours = await testGroupedChart.getChart('hour', 3, 'alice'); + const aliceChartDays = await testGroupedChart.getChart('day', 3, 'alice'); + const bobChartHours = await testGroupedChart.getChart('hour', 3, 'bob'); + const bobChartDays = await testGroupedChart.getChart('day', 3, '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(async () => { + await testUniqueChart.uniqueIncrement('alice'); + await testUniqueChart.uniqueIncrement('alice'); + await testUniqueChart.uniqueIncrement('bob'); + + const chartHours = await testUniqueChart.getChart('hour', 3); + const chartDays = await testUniqueChart.getChart('day', 3); + + assert.deepStrictEqual(chartHours, { + foo: [2, 0, 0], + }); + + assert.deepStrictEqual(chartDays, { + foo: [2, 0, 0], + }); + })); + }); +}); diff --git a/test/mfm.ts b/test/mfm.ts index 191ee5e0ed..69260a5415 100644 --- a/test/mfm.ts +++ b/test/mfm.ts @@ -6,6 +6,10 @@ * * To specify test: * > mocha test/mfm.ts --require ts-node/register -g 'test name' + * + * If the tests not start, try set following enviroment variables: + * TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true + * for more details, please see: https://github.com/TypeStrong/ts-node/issues/754 */ import * as assert from 'assert'; diff --git a/test/mocha.opts b/test/mocha.opts index 907011807d..e114c53bd8 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1 +1,2 @@ ---timeout 10000 +--timeout 30000 +--slow 1000 diff --git a/test/mute.ts b/test/mute.ts new file mode 100644 index 0000000000..bf24b55ee5 --- /dev/null +++ b/test/mute.ts @@ -0,0 +1,170 @@ +/* + * Tests of mute + * + * How to run the tests: + * > mocha test/mute.ts --require ts-node/register + * + * To specify test: + * > mocha test/mute.ts --require ts-node/register -g 'test name' + * + * If the tests not start, try set following enviroment variables: + * TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true + * for more details, please see: https://github.com/TypeStrong/ts-node/issues/754 + */ + +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import * as childProcess from 'child_process'; +import { async, signup, request, post, react, connectStream } from './utils'; + +describe('Mute', () => { + let p: childProcess.ChildProcess; + + // alice mutes carol + let alice: any; + let bob: any; + let carol: any; + + before(done => { + p = childProcess.spawn('node', [__dirname + '/../index.js'], { + stdio: ['inherit', 'inherit', 'ipc'], + env: { NODE_ENV: 'test' } + }); + p.on('message', async message => { + if (message === 'ok') { + (p.channel as any).onread = () => {}; + alice = await signup({ username: 'alice' }); + bob = await signup({ username: 'bob' }); + carol = await signup({ username: 'carol' }); + done(); + } + }); + }); + + after(() => { + p.kill(); + }); + + 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 イベントが流れてこない', () => new Promise(async done => { + // 状態リセット + await request('/i/read-all-unread-notes', {}, alice); + + let fired = false; + + const ws = await connectStream(alice, 'main', ({ type }) => { + if (type == 'unreadMention') { + fired = true; + } + }); + + post(carol, { text: '@alice hi' }); + + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 5000); + })); + + it('ミュートしているユーザーからメンションされても、ストリームに unreadNotification イベントが流れてこない', () => new Promise(async done => { + // 状態リセット + await request('/i/read-all-unread-notes', {}, alice); + await request('/notifications/mark-all-as-read', {}, alice); + + let fired = false; + + const ws = await connectStream(alice, 'main', ({ type }) => { + if (type == 'unreadNotification') { + fired = true; + } + }); + + post(carol, { text: '@alice hi' }); + + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 5000); + })); + + 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); + })); + }); +}); diff --git a/test/note.ts b/test/note.ts new file mode 100644 index 0000000000..7a05930eae --- /dev/null +++ b/test/note.ts @@ -0,0 +1,361 @@ +/* + * Tests of Note + * + * How to run the tests: + * > mocha test/note.ts --require ts-node/register + * + * To specify test: + * > mocha test/note.ts --require ts-node/register -g 'test name' + * + * If the tests not start, try set following enviroment variables: + * TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true + * for more details, please see: https://github.com/TypeStrong/ts-node/issues/754 + */ + +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import * as childProcess from 'child_process'; +import { async, signup, request, post, uploadFile } from './utils'; +import { Note } from '../built/models/entities/note'; +const initDb = require('../built/db/postgre.js').initDb; + +describe('Note', () => { + let p: childProcess.ChildProcess; + let Notes: any; + + let alice: any; + let bob: any; + + before(done => { + p = childProcess.spawn('node', [__dirname + '/../index.js'], { + stdio: ['inherit', 'inherit', 'ipc'], + env: { NODE_ENV: 'test' } + }); + p.on('message', message => { + if (message === 'ok') { + (p.channel as any).onread = () => {}; + initDb(true).then(async connection => { + Notes = connection.getRepository(Note); + alice = await signup({ username: 'alice' }); + bob = await signup({ username: 'bob' }); + done(); + }); + } + }); + }); + + after(() => { + p.kill(); + }); + + 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 uploadFile(alice); + + 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 uploadFile(bob); + + 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, 400); + })); + + 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(1000) + }; + const res = await request('/notes/create', post, alice); + assert.strictEqual(res.status, 200); + })); + + it('文字数オーバーで怒られる', async(async () => { + const post = { + text: '!'.repeat(1001) + }; + 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, 400); + })); + + it('存在しないrenote対象で怒られる', async(async () => { + const post = { + renoteId: '000000000000000000000000' + }; + const res = await request('/notes/create', post, alice); + assert.strictEqual(res.status, 400); + })); + + it('不正なリプライ先IDで怒られる', async(async () => { + const post = { + text: 'test', + replyId: 'foo' + }; + const res = await request('/notes/create', post, alice); + assert.strictEqual(res.status, 400); + })); + + it('不正なrenote対象IDで怒られる', async(async () => { + const post = { + renoteId: 'foo' + }; + const res = await request('/notes/create', post, alice); + assert.strictEqual(res.status, 400); + })); + + 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.findOne(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, 400); + })); + + 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); + })); + }); +}); diff --git a/test/reaction-lib.ts b/test/reaction-lib.ts index 2f6c8ea81b..3a7ff1ab33 100644 --- a/test/reaction-lib.ts +++ b/test/reaction-lib.ts @@ -6,6 +6,10 @@ * * To specify test: * > mocha test/reaction-lib.ts --require ts-node/register -g 'test name' + * + * If the tests not start, try set following enviroment variables: + * TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true + * for more details, please see: https://github.com/TypeStrong/ts-node/issues/754 */ /* diff --git a/test/streaming.ts b/test/streaming.ts index 500324d520..74a5aaa0b4 100644 --- a/test/streaming.ts +++ b/test/streaming.ts @@ -6,143 +6,844 @@ * * To specify test: * > mocha test/streaming.ts --require ts-node/register -g 'test name' + * + * If the tests not start, try set following enviroment variables: + * TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true + * for more details, please see: https://github.com/TypeStrong/ts-node/issues/754 */ -import * as http from 'http'; -import * as WebSocket from 'ws'; +process.env.NODE_ENV = 'test'; + import * as assert from 'assert'; -import { _signup, _request, _uploadFile, _post, _react, resetDb } from './utils'; +import * as childProcess from 'child_process'; +import { connectStream, signup, request, post } from './utils'; +import { Following } from '../built/models/entities/following'; +const initDb = require('../built/db/postgre.js').initDb; -//#region process -Error.stackTraceLimit = Infinity; +describe('Streaming', () => { + let p: childProcess.ChildProcess; + let Followings: any; -// During the test the env variable is set to test -process.env.NODE_ENV = 'test'; + beforeEach(done => { + p = childProcess.spawn('node', [__dirname + '/../index.js'], { + stdio: ['inherit', 'inherit', 'ipc'], + env: { NODE_ENV: 'test' } + }); + p.on('message', message => { + if (message === 'ok') { + (p.channel as any).onread = () => {}; + initDb(true).then(async connection => { + Followings = connection.getRepository(Following); + done(); + }); + } + }); + }); -// Display detail of unhandled promise rejection -process.on('unhandledRejection', console.dir); -//#endregion + afterEach(() => { + p.kill(); + }); -const app = require('../built/server/api').default; -const server = require('../built/server').startServer(); -const db = require('../built/db/mongodb').default; + 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 + }); + }; -const apiServer = http.createServer(app.callback()); + it('mention event', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); -//#region Utilities -const request = _request(apiServer); -const signup = _signup(request); -const post = _post(request); -//#endregion + const ws = await connectStream(bob, 'main', ({ type, body }) => { + if (type == 'mention') { + assert.deepStrictEqual(body.userId, alice.id); + ws.close(); + done(); + } + }); -describe('Streaming', () => { - // Reset database each test - beforeEach(resetDb(db)); + post(alice, { + text: 'foo @bob bar' + }); + })); + + it('renote event', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + const bobNote = await post(bob, { + text: 'foo' + }); + + const ws = await connectStream(bob, 'main', ({ type, body }) => { + if (type == 'renote') { + assert.deepStrictEqual(body.renoteId, bobNote.id); + ws.close(); + done(); + } + }); + + post(alice, { + renoteId: bobNote.id + }); + })); + + describe('Home Timeline', () => { + it('自分の投稿が流れる', () => new Promise(async done => { + const post = { + text: 'foo' + }; + + const me = await signup(); + + const ws = await connectStream(me, 'homeTimeline', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.text, post.text); + ws.close(); + done(); + } + }); + + request('/notes/create', post, me); + })); + + it('フォローしているユーザーの投稿が流れる', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + + // Alice が Bob をフォロー + await request('/following/create', { + userId: bob.id + }, alice); + + const ws = await connectStream(alice, 'homeTimeline', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.userId, bob.id); + ws.close(); + done(); + } + }); + + post(bob, { + text: 'foo' + }); + })); + + it('フォローしていないユーザーの投稿は流れない', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + + let fired = false; + + const ws = await connectStream(alice, 'homeTimeline', ({ type, body }) => { + if (type == 'note') { + fired = true; + } + }); + + post(bob, { + text: 'foo' + }); + + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 3000); + })); + + it('フォローしているユーザーのダイレクト投稿が流れる', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + + // Alice が Bob をフォロー + await request('/following/create', { + userId: bob.id + }, alice); + + const ws = await connectStream(alice, 'homeTimeline', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.userId, bob.id); + assert.deepStrictEqual(body.text, 'foo'); + ws.close(); + done(); + } + }); + + // Bob が Alice 宛てのダイレクト投稿 + post(bob, { + text: 'foo', + visibility: 'specified', + visibleUserIds: [alice.id] + }); + })); + + it('フォローしているユーザーでも自分が指定されていないダイレクト投稿は流れない', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + const carol = await signup({ username: 'carol' }); + + // Alice が Bob をフォロー + await request('/following/create', { + userId: bob.id + }, alice); + + let fired = false; + + const ws = await connectStream(alice, 'homeTimeline', ({ type, body }) => { + if (type == 'note') { + fired = true; + } + }); - after(() => { - server.close(); + // Bob が Carol 宛てのダイレクト投稿 + post(bob, { + text: 'foo', + visibility: 'specified', + visibleUserIds: [carol.id] + }); + + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 3000); + })); }); - it('投稿がタイムラインに流れる', () => new Promise(async done => { - const post = { - text: 'foo' - }; + describe('Local Timeline', () => { + it('自分の投稿が流れる', () => new Promise(async done => { + const me = await signup(); + + const ws = await connectStream(me, 'localTimeline', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.userId, me.id); + ws.close(); + done(); + } + }); + + post(me, { + text: 'foo' + }); + })); - const me = await signup(); - const ws = new WebSocket(`ws://localhost/streaming?i=${me.token}`); + it('フォローしていないローカルユーザーの投稿が流れる', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); - ws.on('open', () => { - ws.on('message', data => { - const msg = JSON.parse(data.toString()); - if (msg.type == 'channel' && msg.body.id == 'a') { - if (msg.body.type == 'note') { - assert.deepStrictEqual(msg.body.body.text, post.text); - ws.close(); - done(); - } - } else if (msg.type == 'connected' && msg.body.id == 'a') { - request('/notes/create', post, me); + const ws = await connectStream(alice, 'localTimeline', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.userId, bob.id); + ws.close(); + done(); } }); - ws.send(JSON.stringify({ - type: 'connect', - body: { - channel: 'homeTimeline', - id: 'a', - pong: true + post(bob, { + text: 'foo' + }); + })); + + it('リモートユーザーの投稿は流れない', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob', host: 'example.com' }); + + let fired = false; + + const ws = await connectStream(alice, 'localTimeline', ({ type, body }) => { + if (type == 'note') { + fired = true; } - })); - }); - })); + }); - it('mention event', () => new Promise(async done => { - const alice = await signup({ username: 'alice' }); - const bob = await signup({ username: 'bob' }); - const aliceNote = { - text: 'foo @bob bar' - }; + post(bob, { + text: 'foo' + }); - const ws = new WebSocket(`ws://localhost/streaming?i=${bob.token}`); + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 3000); + })); - ws.on('open', () => { - ws.on('message', data => { - const msg = JSON.parse(data.toString()); - if (msg.type == 'channel' && msg.body.id == 'a') { - if (msg.body.type == 'mention') { - assert.deepStrictEqual(msg.body.body.text, aliceNote.text); - ws.close(); - done(); - } - } else if (msg.type == 'connected' && msg.body.id == 'a') { - request('/notes/create', aliceNote, alice); + it('フォローしてたとしてもリモートユーザーの投稿は流れない', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob', host: 'example.com' }); + + // Alice が Bob をフォロー + await request('/following/create', { + userId: bob.id + }, alice); + + let fired = false; + + const ws = await connectStream(alice, 'localTimeline', ({ type, body }) => { + if (type == 'note') { + fired = true; } }); - ws.send(JSON.stringify({ - type: 'connect', - body: { - channel: 'main', - id: 'a', - pong: true + post(bob, { + text: 'foo' + }); + + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 3000); + })); + + it('ホーム指定の投稿は流れない', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + + let fired = false; + + const ws = await connectStream(alice, 'localTimeline', ({ type, body }) => { + if (type == 'note') { + fired = true; } - })); - }); - })); + }); - it('renote event', () => new Promise(async done => { - const alice = await signup({ username: 'alice' }); - const bob = await signup({ username: 'bob' }); - const bobNote = await post(bob, { - text: 'foo' - }); + // ホーム指定 + post(bob, { + text: 'foo', + visibility: 'home' + }); + + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 3000); + })); + + it('フォローしているローカルユーザーのダイレクト投稿が流れる', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); - const ws = new WebSocket(`ws://localhost/streaming?i=${bob.token}`); + // Alice が Bob をフォロー + await request('/following/create', { + userId: bob.id + }, alice); - ws.on('open', () => { - ws.on('message', data => { - const msg = JSON.parse(data.toString()); - if (msg.type == 'channel' && msg.body.id == 'a') { - if (msg.body.type == 'renote') { - assert.deepStrictEqual(msg.body.body.renoteId, bobNote.id); - ws.close(); - done(); - } - } else if (msg.type == 'connected' && msg.body.id == 'a') { - request('/notes/create', { - renoteId: bobNote.id - }, alice); + const ws = await connectStream(alice, 'localTimeline', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.userId, bob.id); + assert.deepStrictEqual(body.text, 'foo'); + ws.close(); + done(); } }); - ws.send(JSON.stringify({ - type: 'connect', - body: { - channel: 'main', - id: 'a', - pong: true + // Bob が Alice 宛てのダイレクト投稿 + post(bob, { + text: 'foo', + visibility: 'specified', + visibleUserIds: [alice.id] + }); + })); + + it('フォローしていないローカルユーザーのフォロワー宛て投稿は流れない', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + + let fired = false; + + const ws = await connectStream(alice, 'localTimeline', ({ type, body }) => { + if (type == 'note') { + fired = true; } - })); - }); - })); + }); + + // フォロワー宛て投稿 + post(bob, { + text: 'foo', + visibility: 'followers' + }); + + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 3000); + })); + }); + + describe('Social Timeline', () => { + it('自分の投稿が流れる', () => new Promise(async done => { + const me = await signup(); + + const ws = await connectStream(me, 'socialTimeline', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.userId, me.id); + ws.close(); + done(); + } + }); + + post(me, { + text: 'foo' + }); + })); + + it('フォローしていないローカルユーザーの投稿が流れる', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + + const ws = await connectStream(alice, 'socialTimeline', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.userId, bob.id); + ws.close(); + done(); + } + }); + + post(bob, { + text: 'foo' + }); + })); + + it('フォローしているリモートユーザーの投稿が流れる', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob', host: 'example.com' }); + + // Alice が Bob をフォロー + await follow(alice, bob); + + const ws = await connectStream(alice, 'socialTimeline', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.userId, bob.id); + ws.close(); + done(); + } + }); + + post(bob, { + text: 'foo' + }); + })); + + it('フォローしていないリモートユーザーの投稿は流れない', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob', host: 'example.com' }); + + let fired = false; + + const ws = await connectStream(alice, 'socialTimeline', ({ type, body }) => { + if (type == 'note') { + fired = true; + } + }); + + post(bob, { + text: 'foo' + }); + + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 3000); + })); + + it('フォローしているユーザーのダイレクト投稿が流れる', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + + // Alice が Bob をフォロー + await request('/following/create', { + userId: bob.id + }, alice); + + const ws = await connectStream(alice, 'socialTimeline', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.userId, bob.id); + assert.deepStrictEqual(body.text, 'foo'); + ws.close(); + done(); + } + }); + + // Bob が Alice 宛てのダイレクト投稿 + post(bob, { + text: 'foo', + visibility: 'specified', + visibleUserIds: [alice.id] + }); + })); + + it('フォローしていないローカルユーザーのフォロワー宛て投稿は流れない', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + + let fired = false; + + const ws = await connectStream(alice, 'socialTimeline', ({ type, body }) => { + if (type == 'note') { + fired = true; + } + }); + + // フォロワー宛て投稿 + post(bob, { + text: 'foo', + visibility: 'followers' + }); + + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 3000); + })); + }); + + describe('Global Timeline', () => { + it('フォローしていないローカルユーザーの投稿が流れる', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + + const ws = await connectStream(alice, 'globalTimeline', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.userId, bob.id); + ws.close(); + done(); + } + }); + + post(bob, { + text: 'foo' + }); + })); + + it('フォローしていないリモートユーザーの投稿が流れる', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob', host: 'example.com' }); + + const ws = await connectStream(alice, 'globalTimeline', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.userId, bob.id); + ws.close(); + done(); + } + }); + + post(bob, { + text: 'foo' + }); + })); + }); + + describe('UserList Timeline', () => { + it('リストに入れているユーザーの投稿が流れる', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + + // リスト作成 + const list = await request('/users/lists/create', { + title: 'my list' + }, alice).then(x => x.body); + + // Alice が Bob をリスイン + await request('/users/lists/push', { + listId: list.id, + userId: bob.id + }, alice); + + const ws = await connectStream(alice, 'userList', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.userId, bob.id); + ws.close(); + done(); + } + }, { + listId: list.id + }); + + post(bob, { + text: 'foo' + }); + })); + + it('リストに入れていないユーザーの投稿は流れない', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + + // リスト作成 + const list = await request('/users/lists/create', { + title: 'my list' + }, alice).then(x => x.body); + + let fired = false; + + const ws = await connectStream(alice, 'userList', ({ type, body }) => { + if (type == 'note') { + fired = true; + } + }, { + listId: list.id + }); + + post(bob, { + text: 'foo' + }); + + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 3000); + })); + + // #4471 + it('リストに入れているユーザーのダイレクト投稿が流れる', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + + // リスト作成 + const list = await request('/users/lists/create', { + title: 'my list' + }, alice).then(x => x.body); + + // Alice が Bob をリスイン + await request('/users/lists/push', { + listId: list.id, + userId: bob.id + }, alice); + + const ws = await connectStream(alice, 'userList', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.userId, bob.id); + assert.deepStrictEqual(body.text, 'foo'); + ws.close(); + done(); + } + }, { + listId: list.id + }); + + // Bob が Alice 宛てのダイレクト投稿 + post(bob, { + text: 'foo', + visibility: 'specified', + visibleUserIds: [alice.id] + }); + })); + + // #4335 + it('リストに入れているがフォローはしてないユーザーのフォロワー宛て投稿は流れない', () => new Promise(async done => { + const alice = await signup({ username: 'alice' }); + const bob = await signup({ username: 'bob' }); + + // リスト作成 + const list = await request('/users/lists/create', { + title: 'my list' + }, alice).then(x => x.body); + + // Alice が Bob をリスイン + await request('/users/lists/push', { + listId: list.id, + userId: bob.id + }, alice); + + let fired = false; + + const ws = await connectStream(alice, 'userList', ({ type, body }) => { + if (type == 'note') { + fired = true; + } + }, { + listId: list.id + }); + + // フォロワー宛て投稿 + post(bob, { + text: 'foo', + visibility: 'followers' + }); + + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 3000); + })); + }); + + describe('Hashtag Timeline', () => { + it('指定したハッシュタグの投稿が流れる', () => new Promise(async done => { + const me = await signup(); + + const ws = await connectStream(me, 'hashtag', ({ type, body }) => { + if (type == 'note') { + assert.deepStrictEqual(body.text, '#foo'); + ws.close(); + done(); + } + }, { + q: [ + ['foo'] + ] + }); + + post(me, { + text: '#foo' + }); + })); + + it('指定したハッシュタグの投稿が流れる (AND)', () => new Promise(async done => { + const me = await signup(); + + let fooCount = 0; + let barCount = 0; + let fooBarCount = 0; + + const ws = await connectStream(me, '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(me, { + text: '#foo' + }); + + post(me, { + text: '#bar' + }); + + post(me, { + 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 => { + const me = await signup(); + + let fooCount = 0; + let barCount = 0; + let fooBarCount = 0; + let piyoCount = 0; + + const ws = await connectStream(me, '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(me, { + text: '#foo' + }); + + post(me, { + text: '#bar' + }); + + post(me, { + text: '#foo #bar' + }); + + post(me, { + 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 => { + const me = await signup(); + + let fooCount = 0; + let barCount = 0; + let fooBarCount = 0; + let piyoCount = 0; + let waaaCount = 0; + + const ws = await connectStream(me, '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(me, { + text: '#foo' + }); + + post(me, { + text: '#bar' + }); + + post(me, { + text: '#foo #bar' + }); + + post(me, { + text: '#piyo' + }); + + post(me, { + 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); + })); + }); }); diff --git a/test/user-notes.ts b/test/user-notes.ts new file mode 100644 index 0000000000..5e457d6692 --- /dev/null +++ b/test/user-notes.ts @@ -0,0 +1,86 @@ +/* + * Tests of Note + * + * How to run the tests: + * > mocha test/user-notes.ts --require ts-node/register + * + * To specify test: + * > mocha test/user-notes.ts --require ts-node/register -g 'test name' + * + * If the tests not start, try set following enviroment variables: + * TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true + * for more details, please see: https://github.com/TypeStrong/ts-node/issues/754 + */ + +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import * as childProcess from 'child_process'; +import { async, signup, request, post, uploadFile } from './utils'; + +describe('users/notes', () => { + let p: childProcess.ChildProcess; + + let alice: any; + let jpgNote: any; + let pngNote: any; + let jpgPngNote: any; + + before(done => { + p = childProcess.spawn('node', [__dirname + '/../index.js'], { + stdio: ['inherit', 'inherit', 'ipc'], + env: { NODE_ENV: 'test' } + }); + p.on('message', async message => { + if (message === 'ok') { + (p.channel as any).onread = () => {}; + + alice = await signup({ username: 'alice' }); + const jpg = await uploadFile(alice, __dirname + '/resources/Lenna.jpg'); + const png = await uploadFile(alice, __dirname + '/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] + }); + + done(); + } + }); + }); + + after(() => { + p.kill(); + }); + + 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); + })); +}); diff --git a/test/utils.ts b/test/utils.ts index 1377122478..fbba9a68c9 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; -import * as http from 'http'; -import * as assert from 'chai'; -assert.use(require('chai-http')); +import * as WebSocket from 'ws'; +const fetch = require('node-fetch'); +import * as req from 'request'; export const async = (fn: Function) => (done: Function) => { fn().then(() => { @@ -11,19 +11,31 @@ export const async = (fn: Function) => (done: Function) => { }); }; -export const _request = (server: http.Server) => async (endpoint: string, params: any, me?: any): Promise<ChaiHttp.Response> => { +export const request = async (endpoint: string, params: any, me?: any): Promise<{ body: any, status: number }> => { const auth = me ? { i: me.token } : {}; - const res = await assert.request(server) - .post(endpoint) - .send(Object.assign(auth, params)); + try { + const res = await fetch('http://localhost:80/api' + endpoint, { + method: 'POST', + body: JSON.stringify(Object.assign(auth, params)) + }); - return res; + const status = res.status; + const body = res.status !== 204 ? await res.json().catch() : null; + + return { + body, status + }; + } catch (e) { + return { + body: null, status: 500 + }; + } }; -export const _signup = (request: ReturnType<typeof _request>) => async (params?: any): Promise<any> => { +export const signup = async (params?: any): Promise<any> => { const q = Object.assign({ username: 'test', password: 'test' @@ -34,50 +46,59 @@ export const _signup = (request: ReturnType<typeof _request>) => async (params?: return res.body; }; -export const _post = (request: ReturnType<typeof _request>) => async (user: any, params?: any): Promise<any> => { +export const post = async (user: any, params?: any): Promise<any> => { const q = Object.assign({ text: 'test' }, params); const res = await request('/notes/create', q, user); - return res.body.createdNote; + return res.body ? res.body.createdNote : null; }; -export const _react = (request: ReturnType<typeof _request>) => async (user: any, note: any, reaction: string): Promise<any> => { +export const react = async (user: any, note: any, reaction: string): Promise<any> => { await request('/notes/reactions/create', { noteId: note.id, reaction: reaction }, user); }; -export const _uploadFile = (server: http.Server) => async (user: any): Promise<any> => { - const res = await assert.request(server) - .post('/drive/files/create') - .field('i', user.token) - .attach('file', fs.readFileSync(__dirname + '/resources/Lenna.png'), 'Lenna.png'); +export const uploadFile = (user: any, path?: string): Promise<any> => new Promise((ok, rej) => { + req.post({ + url: 'http://localhost:80/api/drive/files/create', + formData: { + i: user.token, + file: fs.createReadStream(path || __dirname + '/resources/Lenna.png') + }, + json: true + }, (err, httpResponse, body) => { + ok(body); + }); +}); - return res.body; -}; +export function connectStream(user: any, channel: string, listener: any, params?: any): Promise<WebSocket> { + return new Promise((res, rej) => { + const ws = new WebSocket(`ws://localhost/streaming?i=${user.token}`); -export const resetDb = (db: any) => () => new Promise(res => { - // APIがなにかレスポンスを返した後に、後処理を行う場合があり、 - // レスポンスを受け取ってすぐデータベースをリセットすると - // その後処理と競合し(テスト自体は合格するものの)エラーがコンソールに出力され - // 見た目的に気持ち悪くなるので、後処理が終るのを待つために500msくらい待ってから - // データベースをリセットするようにする - setTimeout(async () => { - await Promise.all([ - db.get('users').drop(), - db.get('notes').drop(), - db.get('driveFiles.files').drop(), - db.get('driveFiles.chunks').drop(), - db.get('driveFolders').drop(), - db.get('apps').drop(), - db.get('accessTokens').drop(), - db.get('authSessions').drop() - ]); + 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); + } + }); - res(); - }, 500); -}); + ws.send(JSON.stringify({ + type: 'connect', + body: { + channel: channel, + id: 'a', + pong: true, + params: params + } + })); + }); + }); +} |