Merge branch 'develop' into allow-suspended-access

This commit is contained in:
syuilo
2023-09-22 16:23:40 +09:00
1210 changed files with 16034 additions and 14774 deletions

View File

@@ -9,8 +9,16 @@ import * as assert from 'assert';
import * as crypto from 'node:crypto';
import cbor from 'cbor';
import * as OTPAuth from 'otpauth';
import { loadConfig } from '../../src/config.js';
import { signup, api, post, react, startServer, waitFire } from '../utils.js';
import { loadConfig } from '@/config.js';
import { api, signup, startServer } from '../utils.js';
import type {
AuthenticationResponseJSON,
AuthenticatorAssertionResponseJSON,
AuthenticatorAttestationResponseJSON,
PublicKeyCredentialCreationOptionsJSON,
PublicKeyCredentialRequestOptionsJSON,
RegistrationResponseJSON,
} from '@simplewebauthn/typescript-types';
import type { INestApplicationContext } from '@nestjs/common';
import type * as misskey from 'misskey-js';
@@ -47,21 +55,20 @@ describe('2要素認証', () => {
const rpIdHash = (): Buffer => {
return crypto.createHash('sha256')
.update(Buffer.from(config.hostname, 'utf-8'))
.update(Buffer.from(config.host, 'utf-8'))
.digest();
};
const keyDoneParam = (param: {
token: string,
keyName: string,
challengeId: string,
challenge: string,
credentialId: Buffer,
creationOptions: PublicKeyCredentialCreationOptionsJSON,
}): {
attestationObject: string,
challengeId: string,
clientDataJSON: string,
token: string,
password: string,
name: string,
credential: RegistrationResponseJSON,
} => {
// A COSE encoded public key
const credentialPublicKey = cbor.encode(new Map<number, unknown>([
@@ -76,7 +83,7 @@ describe('2要素認証', () => {
// AuthenticatorAssertionResponse.authenticatorData
// https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAssertionResponse/authenticatorData
const credentialIdLength = Buffer.allocUnsafe(2);
credentialIdLength.writeUInt16BE(param.credentialId.length);
credentialIdLength.writeUInt16BE(param.credentialId.length, 0);
const authData = Buffer.concat([
rpIdHash(), // rpIdHash(32)
Buffer.from([0x45]), // flags(1)
@@ -88,20 +95,28 @@ describe('2要素認証', () => {
]);
return {
attestationObject: cbor.encode({
fmt: 'none',
attStmt: {},
authData,
}).toString('hex'),
challengeId: param.challengeId,
clientDataJSON: JSON.stringify({
type: 'webauthn.create',
challenge: param.challenge,
origin: config.scheme + '://' + config.host,
androidPackageName: 'org.mozilla.firefox',
}),
password,
token: param.token,
name: param.keyName,
credential: <RegistrationResponseJSON>{
id: param.credentialId.toString('base64url'),
rawId: param.credentialId.toString('base64url'),
response: <AuthenticatorAttestationResponseJSON>{
clientDataJSON: Buffer.from(JSON.stringify({
type: 'webauthn.create',
challenge: param.creationOptions.challenge,
origin: config.scheme + '://' + config.host,
androidPackageName: 'org.mozilla.firefox',
}), 'utf-8').toString('base64url'),
attestationObject: cbor.encode({
fmt: 'none',
attStmt: {},
authData,
}).toString('base64url'),
},
clientExtensionResults: {},
type: 'public-key',
},
};
};
@@ -121,17 +136,12 @@ describe('2要素認証', () => {
const signinWithSecurityKeyParam = (param: {
keyName: string,
challengeId: string,
challenge: string,
credentialId: Buffer,
requestOptions: PublicKeyCredentialRequestOptionsJSON,
}): {
authenticatorData: string,
credentialId: string,
challengeId: string,
clientDataJSON: string,
username: string,
password: string,
signature: string,
credential: AuthenticationResponseJSON,
'g-recaptcha-response'?: string | null,
'hcaptcha-response'?: string | null,
} => {
@@ -144,10 +154,10 @@ describe('2要素認証', () => {
]);
const clientDataJSONBuffer = Buffer.from(JSON.stringify({
type: 'webauthn.get',
challenge: param.challenge,
challenge: param.requestOptions.challenge,
origin: config.scheme + '://' + config.host,
androidPackageName: 'org.mozilla.firefox',
}));
}), 'utf-8');
const hashedclientDataJSON = crypto.createHash('sha256')
.update(clientDataJSONBuffer)
.digest();
@@ -156,13 +166,19 @@ describe('2要素認証', () => {
.update(Buffer.concat([authenticatorData, hashedclientDataJSON]))
.sign(privateKey);
return {
authenticatorData: authenticatorData.toString('hex'),
credentialId: param.credentialId.toString('base64'),
challengeId: param.challengeId,
clientDataJSON: clientDataJSONBuffer.toString('hex'),
username,
password,
signature: signature.toString('hex'),
credential: <AuthenticationResponseJSON>{
id: param.credentialId.toString('base64url'),
rawId: param.credentialId.toString('base64url'),
response: <AuthenticatorAssertionResponseJSON>{
clientDataJSON: clientDataJSONBuffer.toString('base64url'),
authenticatorData: authenticatorData.toString('base64url'),
signature: signature.toString('base64url'),
},
clientExtensionResults: {},
type: 'public-key',
},
'g-recaptcha-response': null,
'hcaptcha-response': null,
};
@@ -191,7 +207,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
assert.strictEqual(doneResponse.status, 204);
assert.strictEqual(doneResponse.status, 200);
const usersShowResponse = await api('/users/show', {
username,
@@ -205,6 +221,12 @@ describe('2要素認証', () => {
});
assert.strictEqual(signinResponse.status, 200);
assert.notEqual(signinResponse.body.i, undefined);
// 後片付け
await api('/i/2fa/unregister', {
password,
token: otpToken(registerResponse.body.secret),
}, alice);
});
test('が設定でき、セキュリティキーでログインできる。', async () => {
@@ -216,25 +238,26 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
assert.strictEqual(doneResponse.status, 204);
assert.strictEqual(doneResponse.status, 200);
const registerKeyResponse = await api('/i/2fa/register-key', {
password,
token: otpToken(registerResponse.body.secret),
}, alice);
assert.strictEqual(registerKeyResponse.status, 200);
assert.notEqual(registerKeyResponse.body.challengeId, undefined);
assert.notEqual(registerKeyResponse.body.rp, undefined);
assert.notEqual(registerKeyResponse.body.challenge, undefined);
const keyName = 'example-key';
const credentialId = crypto.randomBytes(0x41);
const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({
token: otpToken(registerResponse.body.secret),
keyName,
challengeId: registerKeyResponse.body.challengeId,
challenge: registerKeyResponse.body.challenge,
credentialId,
creationOptions: registerKeyResponse.body,
}), alice);
assert.strictEqual(keyDoneResponse.status, 200);
assert.strictEqual(keyDoneResponse.body.id, credentialId.toString('hex'));
assert.strictEqual(keyDoneResponse.body.id, credentialId.toString('base64url'));
assert.strictEqual(keyDoneResponse.body.name, keyName);
const usersShowResponse = await api('/users/show', {
@@ -248,19 +271,23 @@ describe('2要素認証', () => {
});
assert.strictEqual(signinResponse.status, 200);
assert.strictEqual(signinResponse.body.i, undefined);
assert.notEqual(signinResponse.body.challengeId, undefined);
assert.notEqual(signinResponse.body.challenge, undefined);
assert.notEqual(signinResponse.body.securityKeys, undefined);
assert.strictEqual(signinResponse.body.securityKeys[0].id, credentialId.toString('hex'));
assert.notEqual(signinResponse.body.allowCredentials, undefined);
assert.strictEqual(signinResponse.body.allowCredentials[0].id, credentialId.toString('base64url'));
const signinResponse2 = await api('/signin', signinWithSecurityKeyParam({
keyName,
challengeId: signinResponse.body.challengeId,
challenge: signinResponse.body.challenge,
credentialId,
requestOptions: signinResponse.body,
}));
assert.strictEqual(signinResponse2.status, 200);
assert.notEqual(signinResponse2.body.i, undefined);
// 後片付け
await api('/i/2fa/unregister', {
password,
token: otpToken(registerResponse.body.secret),
}, alice);
});
test('が設定でき、セキュリティキーでパスワードレスログインできる。', async () => {
@@ -272,9 +299,10 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
assert.strictEqual(doneResponse.status, 204);
assert.strictEqual(doneResponse.status, 200);
const registerKeyResponse = await api('/i/2fa/register-key', {
token: otpToken(registerResponse.body.secret),
password,
}, alice);
assert.strictEqual(registerKeyResponse.status, 200);
@@ -282,10 +310,10 @@ describe('2要素認証', () => {
const keyName = 'example-key';
const credentialId = crypto.randomBytes(0x41);
const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({
token: otpToken(registerResponse.body.secret),
keyName,
challengeId: registerKeyResponse.body.challengeId,
challenge: registerKeyResponse.body.challenge,
credentialId,
creationOptions: registerKeyResponse.body,
}), alice);
assert.strictEqual(keyDoneResponse.status, 200);
@@ -310,14 +338,19 @@ describe('2要素認証', () => {
const signinResponse2 = await api('/signin', {
...signinWithSecurityKeyParam({
keyName,
challengeId: signinResponse.body.challengeId,
challenge: signinResponse.body.challenge,
credentialId,
requestOptions: signinResponse.body,
}),
password: '',
});
assert.strictEqual(signinResponse2.status, 200);
assert.notEqual(signinResponse2.body.i, undefined);
// 後片付け
await api('/i/2fa/unregister', {
password,
token: otpToken(registerResponse.body.secret),
}, alice);
});
test('が設定でき、設定したセキュリティキーの名前を変更できる。', async () => {
@@ -329,9 +362,10 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
assert.strictEqual(doneResponse.status, 204);
assert.strictEqual(doneResponse.status, 200);
const registerKeyResponse = await api('/i/2fa/register-key', {
token: otpToken(registerResponse.body.secret),
password,
}, alice);
assert.strictEqual(registerKeyResponse.status, 200);
@@ -339,27 +373,33 @@ describe('2要素認証', () => {
const keyName = 'example-key';
const credentialId = crypto.randomBytes(0x41);
const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({
token: otpToken(registerResponse.body.secret),
keyName,
challengeId: registerKeyResponse.body.challengeId,
challenge: registerKeyResponse.body.challenge,
credentialId,
creationOptions: registerKeyResponse.body,
}), alice);
assert.strictEqual(keyDoneResponse.status, 200);
const renamedKey = 'other-key';
const updateKeyResponse = await api('/i/2fa/update-key', {
name: renamedKey,
credentialId: credentialId.toString('hex'),
credentialId: credentialId.toString('base64url'),
}, alice);
assert.strictEqual(updateKeyResponse.status, 200);
const iResponse = await api('/i', {
}, alice);
assert.strictEqual(iResponse.status, 200);
const securityKeys = iResponse.body.securityKeysList.filter(s => s.id === credentialId.toString('hex'));
const securityKeys = iResponse.body.securityKeysList.filter((s: { id: string; }) => s.id === credentialId.toString('base64url'));
assert.strictEqual(securityKeys.length, 1);
assert.strictEqual(securityKeys[0].name, renamedKey);
assert.notEqual(securityKeys[0].lastUsed, undefined);
// 後片付け
await api('/i/2fa/unregister', {
password,
token: otpToken(registerResponse.body.secret),
}, alice);
});
test('が設定でき、設定したセキュリティキーを削除できる。', async () => {
@@ -371,9 +411,10 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
assert.strictEqual(doneResponse.status, 204);
assert.strictEqual(doneResponse.status, 200);
const registerKeyResponse = await api('/i/2fa/register-key', {
token: otpToken(registerResponse.body.secret),
password,
}, alice);
assert.strictEqual(registerKeyResponse.status, 200);
@@ -381,10 +422,10 @@ describe('2要素認証', () => {
const keyName = 'example-key';
const credentialId = crypto.randomBytes(0x41);
const keyDoneResponse = await api('/i/2fa/key-done', keyDoneParam({
token: otpToken(registerResponse.body.secret),
keyName,
challengeId: registerKeyResponse.body.challengeId,
challenge: registerKeyResponse.body.challenge,
credentialId,
creationOptions: registerKeyResponse.body,
}), alice);
assert.strictEqual(keyDoneResponse.status, 200);
@@ -394,6 +435,7 @@ describe('2要素認証', () => {
assert.strictEqual(iResponse.status, 200);
for (const key of iResponse.body.securityKeysList) {
const removeKeyResponse = await api('/i/2fa/remove-key', {
token: otpToken(registerResponse.body.secret),
password,
credentialId: key.id,
}, alice);
@@ -412,6 +454,12 @@ describe('2要素認証', () => {
});
assert.strictEqual(signinResponse.status, 200);
assert.notEqual(signinResponse.body.i, undefined);
// 後片付け
await api('/i/2fa/unregister', {
password,
token: otpToken(registerResponse.body.secret),
}, alice);
});
test('が設定でき、設定解除できる。(パスワードのみでログインできる。)', async () => {
@@ -423,7 +471,7 @@ describe('2要素認証', () => {
const doneResponse = await api('/i/2fa/done', {
token: otpToken(registerResponse.body.secret),
}, alice);
assert.strictEqual(doneResponse.status, 204);
assert.strictEqual(doneResponse.status, 200);
const usersShowResponse = await api('/users/show', {
username,
@@ -432,6 +480,7 @@ describe('2要素認証', () => {
assert.strictEqual(usersShowResponse.body.twoFactorEnabled, true);
const unregisterResponse = await api('/i/2fa/unregister', {
token: otpToken(registerResponse.body.secret),
password,
}, alice);
assert.strictEqual(unregisterResponse.status, 204);
@@ -441,5 +490,11 @@ describe('2要素認証', () => {
});
assert.strictEqual(signinResponse.status, 200);
assert.notEqual(signinResponse.body.i, undefined);
// 後片付け
await api('/i/2fa/unregister', {
password,
token: otpToken(registerResponse.body.secret),
}, alice);
});
});

View File

@@ -721,7 +721,7 @@ describe('クリップ', () => {
await addNote({ clipId: aliceClip.id, noteId: aliceNote.id });
const res = await show({ clipId: aliceClip.id });
assert.strictEqual(res.lastClippedAt, new Date(res.lastClippedAt ?? '').toISOString());
assert.deepStrictEqual(await notes({ clipId: aliceClip.id }), [aliceNote]);
assert.deepStrictEqual((await notes({ clipId: aliceClip.id })).map(x => x.id), [aliceNote.id]);
// 他人の非公開ノートも突っ込める
await addNote({ clipId: aliceClip.id, noteId: bobHomeNote.id });
@@ -861,8 +861,8 @@ describe('クリップ', () => {
bobNote, bobHomeNote,
];
assert.deepStrictEqual(
res.sort(compareBy(s => s.id)),
expects.sort(compareBy(s => s.id)));
res.sort(compareBy(s => s.id)).map(x => x.id),
expects.sort(compareBy(s => s.id)).map(x => x.id));
});
test('を始端IDとlimitで取得できる。', async () => {
@@ -881,8 +881,8 @@ describe('クリップ', () => {
// Promise.allで返ってくる配列はID順で並んでないのでソートして厳密比較
const expects = [noteList[3], noteList[4], noteList[5]];
assert.deepStrictEqual(
res.sort(compareBy(s => s.id)),
expects.sort(compareBy(s => s.id)));
res.sort(compareBy(s => s.id)).map(x => x.id),
expects.sort(compareBy(s => s.id)).map(x => x.id));
});
test('をID範囲指定で取得できる。', async () => {
@@ -901,8 +901,8 @@ describe('クリップ', () => {
// Promise.allで返ってくる配列はID順で並んでないのでソートして厳密比較
const expects = [noteList[2], noteList[3]];
assert.deepStrictEqual(
res.sort(compareBy(s => s.id)),
expects.sort(compareBy(s => s.id)));
res.sort(compareBy(s => s.id)).map(x => x.id),
expects.sort(compareBy(s => s.id)).map(x => x.id));
});
test.todo('Remoteのートもクリップできる。どうテストしよう');
@@ -911,7 +911,7 @@ describe('クリップ', () => {
const bobClip = await create({ isPublic: true }, { user: bob } );
await addNote({ clipId: bobClip.id, noteId: aliceNote.id }, { user: bob });
const res = await notes({ clipId: bobClip.id });
assert.deepStrictEqual(res, [aliceNote]);
assert.deepStrictEqual(res.map(x => x.id), [aliceNote.id]);
});
test('はPublicなクリップなら認証なしでも取得できる。(非公開ートはhideされて返ってくる)', async () => {
@@ -928,8 +928,8 @@ describe('クリップ', () => {
hiddenNote(aliceFollowersNote), hiddenNote(aliceSpecifiedNote),
];
assert.deepStrictEqual(
res.sort(compareBy(s => s.id)),
expects.sort(compareBy(s => s.id)));
res.sort(compareBy(s => s.id)).map(x => x.id),
expects.sort(compareBy(s => s.id)).map(x => x.id));
});
test.todo('ブロック、ミュートされたユーザーからの設定取得etc.');

View File

@@ -9,7 +9,7 @@ import * as assert from 'assert';
// node-fetch only supports it's own Blob yet
// https://github.com/node-fetch/node-fetch/pull/1664
import { Blob } from 'node-fetch';
import { User } from '@/models/index.js';
import { MiUser } from '@/models/_.js';
import { startServer, signup, post, api, uploadFile, simpleGet, initTestDb } from '../utils.js';
import type { INestApplicationContext } from '@nestjs/common';
import type * as misskey from 'misskey-js';
@@ -298,7 +298,7 @@ describe('Endpoints', () => {
assert.strictEqual(res.status, 200);
const connection = await initTestDb(true);
const Users = connection.getRepository(User);
const Users = connection.getRepository(MiUser);
const newBob = await Users.findOneByOrFail({ id: bob.id });
assert.strictEqual(newBob.followersCount, 0);
assert.strictEqual(newBob.followingCount, 1);
@@ -360,7 +360,7 @@ describe('Endpoints', () => {
assert.strictEqual(res.status, 200);
const connection = await initTestDb(true);
const Users = connection.getRepository(User);
const Users = connection.getRepository(MiUser);
const newBob = await Users.findOneByOrFail({ id: bob.id });
assert.strictEqual(newBob.followersCount, 0);
assert.strictEqual(newBob.followingCount, 0);

View File

@@ -34,6 +34,8 @@ describe('Webリソース', () => {
let aliceGalleryPost: any;
let aliceChannel: any;
let bob: misskey.entities.MeSignup;
type Request = {
path: string,
accept?: string,
@@ -90,6 +92,8 @@ describe('Webリソース', () => {
fileIds: [aliceUploadedFile.body.id],
});
aliceChannel = await channel(alice, {});
bob = await signup({ username: 'alice' });
}, 1000 * 60 * 2);
afterAll(async () => {
@@ -163,9 +167,15 @@ describe('Webリソース', () => {
});
describe.each([{ path: '/queue' }])('$path', ({ path }) => {
test('はログインしないとGETできない。', async () => await notOk({
path,
status: 401,
}));
test('はadminでなければGETできない。', async () => await notOk({
path,
status: 500, // FIXME? 403ではない。
cookie: cookie(bob),
status: 403,
}));
test('はadminならGETできる。', async () => await ok({

View File

@@ -7,7 +7,7 @@ process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import { loadConfig } from '@/config.js';
import { User, UsersRepository } from '@/models/index.js';
import { MiUser, UsersRepository } from '@/models/_.js';
import { jobQueue } from '@/boot/common.js';
import { secureRndstr } from '@/misc/secure-rndstr.js';
import { uploadFile, signup, startServer, initTestDb, api, sleep, successfulApiCall } from '../utils.js';
@@ -42,7 +42,7 @@ describe('Account Move', () => {
dave = await signup({ username: 'dave' });
eve = await signup({ username: 'eve' });
frank = await signup({ username: 'frank' });
Users = connection.getRepository(User);
Users = connection.getRepository(MiUser);
}, 1000 * 60 * 2);
afterAll(async () => {

View File

@@ -6,7 +6,7 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import { Note } from '@/models/entities/Note.js';
import { MiNote } from '@/models/Note.js';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { signup, post, uploadUrl, startServer, initTestDb, api, uploadFile } from '../utils.js';
import type { INestApplicationContext } from '@nestjs/common';
@@ -22,7 +22,7 @@ describe('Note', () => {
beforeAll(async () => {
app = await startServer();
const connection = await initTestDb(true);
Notes = connection.getRepository(Note);
Notes = connection.getRepository(MiNote);
alice = await signup({ username: 'alice' });
bob = await signup({ username: 'bob' });
}, 1000 * 60 * 2);

View File

@@ -6,7 +6,7 @@
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import { Following } from '@/models/entities/Following.js';
import { MiFollowing } from '@/models/Following.js';
import { connectStream, signup, api, post, startServer, initTestDb, waitFire } from '../utils.js';
import type { INestApplicationContext } from '@nestjs/common';
import type * as misskey from 'misskey-js';
@@ -46,7 +46,7 @@ describe('Streaming', () => {
beforeAll(async () => {
app = await startServer();
const connection = await initTestDb(true);
Followings = connection.getRepository(Following);
Followings = connection.getRepository(MiFollowing);
ayano = await signup({ username: 'ayano' });
kyoko = await signup({ username: 'kyoko' });

View File

@@ -102,6 +102,7 @@ describe('ユーザー', () => {
birthday: user.birthday,
lang: user.lang,
fields: user.fields,
verifiedLinks: user.verifiedLinks,
followersCount: user.followersCount,
followingCount: user.followingCount,
notesCount: user.notesCount,
@@ -131,6 +132,7 @@ describe('ユーザー', () => {
isBlocked: user.isBlocked ?? false,
isMuted: user.isMuted ?? false,
isRenoteMuted: user.isRenoteMuted ?? false,
notify: user.notify ?? 'none',
});
};
@@ -152,6 +154,7 @@ describe('ユーザー', () => {
preventAiLearning: user.preventAiLearning,
isExplorable: user.isExplorable,
isDeleted: user.isDeleted,
twoFactorBackupCodesStock: user.twoFactorBackupCodesStock,
hideOnlineStatus: user.hideOnlineStatus,
hasUnreadSpecifiedNotes: user.hasUnreadSpecifiedNotes,
hasUnreadMentions: user.hasUnreadMentions,
@@ -368,6 +371,7 @@ describe('ユーザー', () => {
assert.strictEqual(response.birthday, null);
assert.strictEqual(response.lang, null);
assert.deepStrictEqual(response.fields, []);
assert.deepStrictEqual(response.verifiedLinks, []);
assert.strictEqual(response.followersCount, 0);
assert.strictEqual(response.followingCount, 0);
assert.strictEqual(response.notesCount, 0);
@@ -398,6 +402,7 @@ describe('ユーザー', () => {
assert.strictEqual(response.preventAiLearning, true);
assert.strictEqual(response.isExplorable, true);
assert.strictEqual(response.isDeleted, false);
assert.strictEqual(response.twoFactorBackupCodesStock, 'none');
assert.strictEqual(response.hideOnlineStatus, false);
assert.strictEqual(response.hasUnreadSpecifiedNotes, false);
assert.strictEqual(response.hasUnreadMentions, false);
@@ -490,7 +495,7 @@ describe('ユーザー', () => {
{ parameters: (): object => ({ mutedWords: [] }) },
{ parameters: (): object => ({ mutedInstances: ['xxxx.xxxxx'] }) },
{ parameters: (): object => ({ mutedInstances: [] }) },
{ parameters: (): object => ({ mutingNotificationTypes: ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] }) },
{ parameters: (): object => ({ mutingNotificationTypes: ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] }) },
{ parameters: (): object => ({ mutingNotificationTypes: [] }) },
{ parameters: (): object => ({ emailNotificationTypes: ['mention', 'reply', 'quote', 'follow', 'receiveFollowRequest'] }) },
{ parameters: (): object => ({ emailNotificationTypes: [] }) },

View File

@@ -15,7 +15,7 @@ import type { LoggerService } from '@/core/LoggerService.js';
import type { MetaService } from '@/core/MetaService.js';
import type { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import type { NoteReactionsRepository, NotesRepository, PollsRepository, UsersRepository } from '@/models/index.js';
import type { NoteReactionsRepository, NotesRepository, PollsRepository, UsersRepository } from '@/models/_.js';
type MockResponse = {
type: string;

View File

@@ -10,8 +10,8 @@
"declaration": false,
"sourceMap": true,
"target": "ES2022",
"module": "es2020",
"moduleResolution": "node16",
"module": "nodenext",
"moduleResolution": "nodenext",
"allowSyntheticDefaultImports": true,
"removeComments": false,
"noLib": false,

View File

@@ -10,9 +10,9 @@ import { ModuleMocker } from 'jest-mock';
import { Test } from '@nestjs/testing';
import { GlobalModule } from '@/GlobalModule.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
import type { Announcement, AnnouncementsRepository, AnnouncementReadsRepository, UsersRepository, User } from '@/models/index.js';
import type { MiAnnouncement, AnnouncementsRepository, AnnouncementReadsRepository, UsersRepository, MiUser } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { genAid } from '@/misc/id/aid.js';
import { genAidx } from '@/misc/id/aidx.js';
import { CacheService } from '@/core/CacheService.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
@@ -30,10 +30,10 @@ describe('AnnouncementService', () => {
let announcementReadsRepository: AnnouncementReadsRepository;
let globalEventService: jest.Mocked<GlobalEventService>;
function createUser(data: Partial<User> = {}) {
function createUser(data: Partial<MiUser> = {}) {
const un = secureRndstr(16);
return usersRepository.insert({
id: genAid(new Date()),
id: genAidx(new Date()),
createdAt: new Date(),
username: un,
usernameLower: un,
@@ -42,9 +42,9 @@ describe('AnnouncementService', () => {
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
}
function createAnnouncement(data: Partial<Announcement> = {}) {
function createAnnouncement(data: Partial<MiAnnouncement> = {}) {
return announcementsRepository.insert({
id: genAid(new Date()),
id: genAidx(new Date()),
createdAt: new Date(),
updatedAt: null,
title: 'Title',

View File

@@ -6,7 +6,6 @@
process.env.NODE_ENV = 'test';
import { jest } from '@jest/globals';
import { ModuleMocker } from 'jest-mock';
import { Test } from '@nestjs/testing';
import { Redis } from 'ioredis';
import { GlobalModule } from '@/GlobalModule.js';
@@ -18,7 +17,6 @@ import { UtilityService } from '@/core/UtilityService.js';
import { IdService } from '@/core/IdService.js';
import { DI } from '@/di-symbols.js';
import type { TestingModule } from '@nestjs/testing';
import type { MockFunctionMetadata } from 'jest-mock';
function mockRedis() {
const hash = {};
@@ -35,9 +33,9 @@ describe('FetchInstanceMetadataService', () => {
let fetchInstanceMetadataService: jest.Mocked<FetchInstanceMetadataService>;
let federatedInstanceService: jest.Mocked<FederatedInstanceService>;
let httpRequestService: jest.Mocked<HttpRequestService>;
let redisClient: jest.Mocked<Redis.Redis>;
let redisClient: jest.Mocked<Redis>;
beforeAll(async () => {
beforeEach(async () => {
app = await Test
.createTestingModule({
imports: [
@@ -64,11 +62,11 @@ describe('FetchInstanceMetadataService', () => {
fetchInstanceMetadataService = app.get<FetchInstanceMetadataService>(FetchInstanceMetadataService);
federatedInstanceService = app.get<FederatedInstanceService>(FederatedInstanceService) as jest.Mocked<FederatedInstanceService>;
redisClient = app.get<Redis.Redis>(DI.redis) as jest.Mocked<Redis.Redis>;
redisClient = app.get<Redis>(DI.redis) as jest.Mocked<Redis>;
httpRequestService = app.get<HttpRequestService>(HttpRequestService) as jest.Mocked<HttpRequestService>;
});
afterAll(async () => {
afterEach(async () => {
await app.close();
});
@@ -85,6 +83,7 @@ describe('FetchInstanceMetadataService', () => {
expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1);
expect(httpRequestService.getJson).toHaveBeenCalled();
});
test('Lock and don\'t update', async () => {
redisClient.set = mockRedis();
const now = Date.now();
@@ -98,6 +97,7 @@ describe('FetchInstanceMetadataService', () => {
expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1);
expect(httpRequestService.getJson).toHaveBeenCalledTimes(0);
});
test('Do nothing when lock not acquired', async () => {
redisClient.set = mockRedis();
federatedInstanceService.fetch.mockReturnValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } });

View File

@@ -9,7 +9,7 @@ import { jest } from '@jest/globals';
import { ModuleMocker } from 'jest-mock';
import { Test } from '@nestjs/testing';
import { GlobalModule } from '@/GlobalModule.js';
import type { MetasRepository } from '@/models/index.js';
import type { MetasRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js';
import { CoreModule } from '@/core/CoreModule.js';

View File

@@ -15,7 +15,7 @@ import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { QueueService } from '@/core/QueueService.js';
import { IdService } from '@/core/IdService.js';
import type { RelaysRepository } from '@/models/index.js';
import type { RelaysRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import type { TestingModule } from '@nestjs/testing';
import type { MockFunctionMetadata } from 'jest-mock';

View File

@@ -11,10 +11,10 @@ import { Test } from '@nestjs/testing';
import * as lolex from '@sinonjs/fake-timers';
import { GlobalModule } from '@/GlobalModule.js';
import { RoleService } from '@/core/RoleService.js';
import type { Role, RolesRepository, RoleAssignmentsRepository, UsersRepository, User } from '@/models/index.js';
import type { MiRole, RolesRepository, RoleAssignmentsRepository, UsersRepository, MiUser } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js';
import { genAid } from '@/misc/id/aid.js';
import { genAidx } from '@/misc/id/aidx.js';
import { CacheService } from '@/core/CacheService.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
@@ -34,10 +34,10 @@ describe('RoleService', () => {
let metaService: jest.Mocked<MetaService>;
let clock: lolex.InstalledClock;
function createUser(data: Partial<User> = {}) {
function createUser(data: Partial<MiUser> = {}) {
const un = secureRndstr(16);
return usersRepository.insert({
id: genAid(new Date()),
id: genAidx(new Date()),
createdAt: new Date(),
username: un,
usernameLower: un,
@@ -46,9 +46,9 @@ describe('RoleService', () => {
.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
}
function createRole(data: Partial<Role> = {}) {
function createRole(data: Partial<MiRole> = {}) {
return rolesRepository.insert({
id: genAid(new Date()),
id: genAidx(new Date()),
createdAt: new Date(),
updatedAt: new Date(),
lastUsedAt: new Date(),
@@ -204,7 +204,7 @@ describe('RoleService', () => {
createdAt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 365)),
followersCount: 10,
});
const role = await createRole({
await createRole({
name: 'a',
policies: {
canManageCustomEmojis: {

View File

@@ -10,8 +10,8 @@ import { UploadPartCommand, CompleteMultipartUploadCommand, CreateMultipartUploa
import { mockClient } from 'aws-sdk-client-mock';
import { GlobalModule } from '@/GlobalModule.js';
import { CoreModule } from '@/core/CoreModule.js';
import { S3Service } from '@/core/S3Service';
import { Meta } from '@/models';
import { S3Service } from '@/core/S3Service.js';
import { MiMeta } from '@/models/_.js';
import type { TestingModule } from '@nestjs/testing';
describe('S3Service', () => {
@@ -40,7 +40,7 @@ describe('S3Service', () => {
test('upload a file', async () => {
s3Mock.on(PutObjectCommand).resolves({});
await s3Service.upload({ objectStorageRegion: 'us-east-1' } as Meta, {
await s3Service.upload({ objectStorageRegion: 'us-east-1' } as MiMeta, {
Bucket: 'fake',
Key: 'fake',
Body: 'x',
@@ -52,7 +52,7 @@ describe('S3Service', () => {
s3Mock.on(UploadPartCommand).resolves({ ETag: '1' });
s3Mock.on(CompleteMultipartUploadCommand).resolves({ Bucket: 'fake', Key: 'fake' });
await s3Service.upload({} as Meta, {
await s3Service.upload({} as MiMeta, {
Bucket: 'fake',
Key: 'fake',
Body: 'x'.repeat(8 * 1024 * 1024 + 1), // デフォルトpartSizeにしている 8 * 1024 * 1024 を越えるサイズ
@@ -62,7 +62,7 @@ describe('S3Service', () => {
test('upload a file error', async () => {
s3Mock.on(PutObjectCommand).rejects({ name: 'Fake Error' });
await expect(s3Service.upload({ objectStorageRegion: 'us-east-1' } as Meta, {
await expect(s3Service.upload({ objectStorageRegion: 'us-east-1' } as MiMeta, {
Bucket: 'fake',
Key: 'fake',
Body: 'x',
@@ -72,7 +72,7 @@ describe('S3Service', () => {
test('upload a large file error', async () => {
s3Mock.on(UploadPartCommand).rejects();
await expect(s3Service.upload({} as Meta, {
await expect(s3Service.upload({} as MiMeta, {
Bucket: 'fake',
Key: 'fake',
Body: 'x'.repeat(8 * 1024 * 1024 + 1), // デフォルトpartSizeにしている 8 * 1024 * 1024 を越えるサイズ

View File

@@ -18,11 +18,11 @@ import { CoreModule } from '@/core/CoreModule.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { LoggerService } from '@/core/LoggerService.js';
import type { IActor, IApDocument, ICollection, IPost } from '@/core/activitypub/type.js';
import { Meta, Note } from '@/models/index.js';
import { MiMeta, MiNote } from '@/models/_.js';
import { secureRndstr } from '@/misc/secure-rndstr.js';
import { DownloadService } from '@/core/DownloadService.js';
import { MetaService } from '@/core/MetaService.js';
import type { RemoteUser } from '@/models/entities/User.js';
import type { MiRemoteUser } from '@/models/User.js';
import { MockResolver } from '../misc/mock-resolver.js';
const host = 'https://host1.test';
@@ -75,7 +75,7 @@ function createRandomFeaturedCollection(actor: NonTransientIActor, length: numbe
async function createRandomRemoteUser(
resolver: MockResolver,
personService: ApPersonService,
): Promise<RemoteUser> {
): Promise<MiRemoteUser> {
const actor = createRandomActor();
resolver.register(actor.id, actor);
@@ -94,7 +94,7 @@ describe('ActivityPub', () => {
cacheRemoteSensitiveFiles: true,
blockedHosts: [] as string[],
sensitiveWords: [] as string[],
} as Meta;
} as MiMeta;
let meta = metaInitial;
beforeAll(async () => {
@@ -109,7 +109,7 @@ describe('ActivityPub', () => {
},
})
.overrideProvider(MetaService).useValue({
async fetch(): Promise<Meta> {
async fetch(): Promise<MiMeta> {
return meta;
},
}).compile();
@@ -199,7 +199,7 @@ describe('ActivityPub', () => {
rendererService.renderAnnounce(null, {
createdAt: new Date(0),
visibility: 'followers',
} as Note);
} as MiNote);
});
});

View File

@@ -18,7 +18,7 @@ import { entity as TestGroupedChartEntity } from '@/core/chart/charts/entities/t
import { entity as TestUniqueChartEntity } from '@/core/chart/charts/entities/test-unique.js';
import { entity as TestIntersectionChartEntity } from '@/core/chart/charts/entities/test-intersection.js';
import { loadConfig } from '@/config.js';
import type { AppLockService } from '@/core/AppLockService';
import type { AppLockService } from '@/core/AppLockService.js';
import Logger from '@/logger.js';
describe('Chart', () => {

View File

@@ -6,42 +6,42 @@
import { correctFilename } from '@/misc/correct-filename.js';
describe(correctFilename, () => {
it('no ext to null', () => {
expect(correctFilename('test', null)).toBe('test.unknown');
});
it('no ext to jpg', () => {
expect(correctFilename('test', 'jpg')).toBe('test.jpg');
});
it('jpg to webp', () => {
expect(correctFilename('test.jpg', 'webp')).toBe('test.jpg.webp');
});
it('jpg to .webp', () => {
expect(correctFilename('test.jpg', '.webp')).toBe('test.jpg.webp');
});
it('jpeg to jpg', () => {
expect(correctFilename('test.jpeg', 'jpg')).toBe('test.jpeg');
});
it('JPEG to jpg', () => {
expect(correctFilename('test.JPEG', 'jpg')).toBe('test.JPEG');
});
it('jpg to jpg', () => {
expect(correctFilename('test.jpg', 'jpg')).toBe('test.jpg');
});
it('JPG to jpg', () => {
expect(correctFilename('test.JPG', 'jpg')).toBe('test.JPG');
});
it('tiff to tif', () => {
expect(correctFilename('test.tiff', 'tif')).toBe('test.tiff');
});
it('skip gz', () => {
expect(correctFilename('test.unitypackage', 'gz')).toBe('test.unitypackage');
});
it('skip text file', () => {
expect(correctFilename('test.txt', null)).toBe('test.txt');
});
it('unknown', () => {
expect(correctFilename('test.hoge', null)).toBe('test.hoge');
});
it('no ext to null', () => {
expect(correctFilename('test', null)).toBe('test.unknown');
});
it('no ext to jpg', () => {
expect(correctFilename('test', 'jpg')).toBe('test.jpg');
});
it('jpg to webp', () => {
expect(correctFilename('test.jpg', 'webp')).toBe('test.jpg.webp');
});
it('jpg to .webp', () => {
expect(correctFilename('test.jpg', '.webp')).toBe('test.jpg.webp');
});
it('jpeg to jpg', () => {
expect(correctFilename('test.jpeg', 'jpg')).toBe('test.jpeg');
});
it('JPEG to jpg', () => {
expect(correctFilename('test.JPEG', 'jpg')).toBe('test.JPEG');
});
it('jpg to jpg', () => {
expect(correctFilename('test.jpg', 'jpg')).toBe('test.jpg');
});
it('JPG to jpg', () => {
expect(correctFilename('test.JPG', 'jpg')).toBe('test.JPG');
});
it('tiff to tif', () => {
expect(correctFilename('test.tiff', 'tif')).toBe('test.tiff');
});
it('skip gz', () => {
expect(correctFilename('test.unitypackage', 'gz')).toBe('test.unitypackage');
});
it('skip text file', () => {
expect(correctFilename('test.txt', null)).toBe('test.txt');
});
it('unknown', () => {
expect(correctFilename('test.hoge', null)).toBe('test.hoge');
});
test('non ascii with space', () => {
expect(correctFilename('ファイル 名前', 'jpg')).toBe('ファイル 名前.jpg');
});

View File

@@ -6,6 +6,7 @@
import { ulid } from 'ulid';
import { describe, test, expect } from '@jest/globals';
import { aidRegExp, genAid, parseAid } from '@/misc/id/aid.js';
import { aidxRegExp, genAidx, parseAidx } from '@/misc/id/aidx.js';
import { genMeid, meidRegExp, parseMeid } from '@/misc/id/meid.js';
import { genMeidg, meidgRegExp, parseMeidg } from '@/misc/id/meidg.js';
import { genObjectId, objectIdRegExp, parseObjectId } from '@/misc/id/object-id.js';
@@ -19,6 +20,13 @@ describe('misc:id', () => {
expect(parseAid(gotAid).date.getTime()).toBe(date.getTime());
});
test('aidx', () => {
const date = new Date();
const gotAidx = genAidx(date);
expect(gotAidx).toMatch(aidxRegExp);
expect(parseAidx(gotAidx).date.getTime()).toBe(date.getTime());
});
test('meid', () => {
const date = new Date();
const gotMeid = genMeid(date);

View File

@@ -8,7 +8,7 @@ import { readFile } from 'node:fs/promises';
import { isAbsolute, basename } from 'node:path';
import { inspect } from 'node:util';
import WebSocket, { ClientOptions } from 'ws';
import fetch, { Blob, File, RequestInit } from 'node-fetch';
import fetch, { File, RequestInit } from 'node-fetch';
import { DataSource } from 'typeorm';
import { JSDOM } from 'jsdom';
import { DEFAULT_POLICIES } from '@/core/RoleService.js';