diff --git a/src/domain/post.test.ts b/src/domain/post.test.ts new file mode 100644 index 0000000..9431915 --- /dev/null +++ b/src/domain/post.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "vitest"; +import { Post, PostArgs } from "./post.js"; +import { Snowflake } from "../helpers/id_generator.js"; +import { Media } from "./media.js"; + +describe("Post", () => { + const defaultPostArgs: PostArgs = { + id: "10101450391945216" as Snowflake, + authorID: "10101451896913920" as Snowflake, + text: "Hello, world!", + visibility: 0, + createdAt: new Date(), + attachments: [], + reactions: [], + }; + + it("添付ファイルは16個以上添付できない", () => { + expect(() => { + new Post({ + ...defaultPostArgs, + attachments: new Array(17).fill({} as Media), + }); + }).toThrowError( + new Error( + "failed to create post: The number of attachments must be less than 16.", + ), + ); + }); + + it("ファイルを添付できる", () => { + expect(() => { + new Post({ + ...defaultPostArgs, + attachments: new Array(10).fill({} as Media), + }); + }).toBeTruthy(); + }); +}); diff --git a/src/domain/post.ts b/src/domain/post.ts index 46bf32f..c949194 100644 --- a/src/domain/post.ts +++ b/src/domain/post.ts @@ -2,6 +2,16 @@ import { Snowflake } from "../helpers/id_generator.js"; import { User } from "./user.js"; import { Media } from "./media.js"; +export interface PostArgs { + id: Snowflake; + authorID: Snowflake; + text: string; + visibility: number; + createdAt: Date; + attachments: Array; + reactions: Array; +} + export class Post { get visibility(): number { return this._visibility; @@ -54,6 +64,7 @@ export class Post { attachments: Array; reactions: Array; }) { + this.validate(args); this._id = args.id; this._authorID = args.authorID; @@ -70,6 +81,14 @@ export class Post { } return text; } + + private validate(args: PostArgs) { + if (args.attachments.length > 16) { + throw new Error( + "failed to create post: The number of attachments must be less than 16.", + ); + } + } } export class PostReactionEvent { diff --git a/src/domain/server.test.ts b/src/domain/server.test.ts new file mode 100644 index 0000000..65274ce --- /dev/null +++ b/src/domain/server.test.ts @@ -0,0 +1,116 @@ +import { describe, expect, it } from "vitest"; +import { Server, ServerArgs } from "./server.js"; +import { Snowflake } from "../helpers/id_generator.js"; + +describe("Server", () => { + const defaultServerArgs: ServerArgs = { + id: "10101294855225344" as Snowflake, + host: "social.example.jp", + softwareName: "Mastodon", + softwareVersion: "4.0.0", + name: "example social", + description: "this is example social", + maintainer: "example maintainer", + maintainerEmail: "johndoe@example.jp", + iconURL: "https://social.example.jp/icon.png", + faviconURL: "https://social.example.jp/favicon.png", + }; + + it("128文字以上のホスト名は設定できない", () => { + expect(() => { + new Server({ + ...defaultServerArgs, + host: "a".repeat(128 + 1), + }); + }).toThrowError(new Error("failed to create server: host is too long")); + }); + + it("4文字以下のホスト名は設定できない", () => { + expect(() => { + new Server({ + ...defaultServerArgs, + host: "a.b", + }); + }).toThrowError(new Error("failed to create server: host is too short")); + }); + + it("ホスト名を設定できる", () => { + expect(() => { + new Server({ + ...defaultServerArgs, + }); + }).toBeTruthy(); + }); + + it("128文字以上のソフトウェア名は設定できない", () => { + expect(() => { + new Server({ + ...defaultServerArgs, + softwareName: "a".repeat(128 + 1), + }); + }).toThrowError( + new Error("failed to create server: softwareName is too long"), + ); + }); + + it("ソフトウェア名を設定できる", () => { + expect(() => { + new Server({ + ...defaultServerArgs, + }); + }).toBeTruthy(); + }); + + it("128文字以上のソフトウェアバージョンは設定できない", () => { + expect(() => { + new Server({ + ...defaultServerArgs, + softwareVersion: "a".repeat(128 + 1), + }); + }).toThrowError( + new Error("failed to create server: softwareVersion is too long"), + ); + }); + + it("128文字以上のサーバー名は設定できない", () => { + expect(() => { + new Server({ + ...defaultServerArgs, + name: "a".repeat(128 + 1), + }); + }).toThrowError(new Error("failed to create server: name is too long")); + }); + + it("3000文字以上のサーバー説明文は設定できない", () => { + expect(() => { + new Server({ + ...defaultServerArgs, + description: "a".repeat(3000 + 1), + }); + }).toThrowError( + new Error("failed to create server: description is too long"), + ); + }); + + it("256文字以上の管理者名は設定できない", () => { + expect(() => { + new Server({ + ...defaultServerArgs, + maintainer: "a".repeat(256 + 1), + }); + }).toThrowError( + new Error("failed to create server: maintainer is too long"), + ); + }); + + it("256文字以上の管理者メールアドレスは設定できない", () => { + expect(() => { + new Server({ + ...defaultServerArgs, + maintainerEmail: "a".repeat(256 + 1), + }); + }).toThrowError( + new Error("failed to create server: maintainerEmail is too long"), + ); + }); +}); diff --git a/src/domain/server.ts b/src/domain/server.ts index af57074..12a2c61 100644 --- a/src/domain/server.ts +++ b/src/domain/server.ts @@ -1,5 +1,18 @@ import { Snowflake } from "../helpers/id_generator.js"; +export interface ServerArgs { + id: Snowflake; + host: string; + softwareName: string; + softwareVersion: string; + name: string; + description: string; + maintainer: string; + maintainerEmail: string; + iconURL: string; + faviconURL: string; +} + export class Server { get softwareName(): string { return this._softwareName; @@ -105,6 +118,8 @@ export class Server { iconURL: string; faviconURL: string; }) { + this.validate(args); + this._id = args.id; this._host = args.host; this._softwareName = args.softwareName; @@ -116,4 +131,36 @@ export class Server { this._iconURL = args.iconURL; this._faviconURL = args.faviconURL; } + + private validate(args: ServerArgs) { + if ([...args.host].length > 128) { + throw new Error("failed to create server: host is too long"); + } else if ([...args.host].length < 4) { + throw new Error("failed to create server: host is too short"); + } + + if ([...args.softwareName].length > 128) { + throw new Error("failed to create server: softwareName is too long"); + } + + if ([...args.softwareVersion].length > 128) { + throw new Error("failed to create server: softwareVersion is too long"); + } + + if ([...args.name].length > 128) { + throw new Error("failed to create server: name is too long"); + } + + if ([...args.description].length > 3000) { + throw new Error("failed to create server: description is too long"); + } + + if ([...args.maintainer].length > 256) { + throw new Error("failed to create server: maintainer is too long"); + } + + if ([...args.maintainerEmail].length > 256) { + throw new Error("failed to create server: maintainerEmail is too long"); + } + } } diff --git a/src/domain/user.test.ts b/src/domain/user.test.ts new file mode 100644 index 0000000..664a152 --- /dev/null +++ b/src/domain/user.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from "vitest"; +import { User, UserAPData } from "./user.js"; +import { Snowflake } from "../helpers/id_generator.js"; + +describe("User domain model", () => { + const defaultDataArgs = { + id: "10100912877076480" as Snowflake, + serverID: "10100914303139840" as Snowflake, + handle: "test", + fullHandle: "test@example.jp", + nickName: "TestUser", + bio: "testのユーザーです", + headerImageURL: "https://image.example.jp/header.png", + iconImageURL: "https://image.example.jp/icon.png", + password: "efnrkgnjkneiug", + role: 0, + createdAt: new Date(), + isLocalUser: true, + following: [], + apData: new UserAPData({ + followersURL: "https://example.jp/followers", + followingURL: "https://example.jp/following", + inboxURL: "https://example.jp/inbox", + outboxURL: "https://example.jp/outbox", + privateKey: "", + publicKey: "", + userAPID: "10100938952278016", + userID: "10100912877076480" as Snowflake, + }), + }; + + it("64文字以内の表示名は設定できる", () => { + const u = new User({ ...defaultDataArgs, nickName: "テストユーザー" }); + expect(u).toBeTruthy(); + }); + + it("64文字以上の表示名は設定できない", () => { + expect( + () => + new User({ + ...defaultDataArgs, + nickName: "テストユーザー".repeat(10), + }), + ).toThrowError(new Error("failed to create user: nickname is too long")); + }); + + it("64文字以上のハンドルは設定できない", () => { + expect( + () => + new User({ + ...defaultDataArgs, + handle: "test_user".repeat(20), + }), + ).toThrowError(new Error("failed to create user: handle is too long")); + }); + + it("64文字以下のハンドルを設定できる", () => { + expect(new User({ ...defaultDataArgs, handle: "test_user" })).toBeTruthy(); + }); + + it("空文字列は設定できない", () => { + expect(() => new User({ ...defaultDataArgs, handle: "" })).toThrowError( + new Error("failed to create user: handle is too short"), + ); + }); + + it("フルハンドルを設定できる", () => { + expect( + new User({ ...defaultDataArgs, fullHandle: "test@social.example.jp" }), + ).toBeTruthy(); + }); + + it("FQDNとして成立しない長さのフルハンドルは設定できない", () => { + expect( + () => + new User({ + ...defaultDataArgs, + fullHandle: "t@a.b", + }), + ).toThrowError(new Error("failed to create user: fullHandle is too short")); + }); + + it("3000文字以上のbioは設定できない", () => { + expect( + () => + new User({ + ...defaultDataArgs, + bio: "a".repeat(3001), + }), + ).toThrowError(new Error("failed to create user: bio is too long")); + }); + + it("bioを設定できる", () => { + expect( + new User({ + ...defaultDataArgs, + bio: "テストユーザーです", + }), + ).toBeTruthy(); + }); +}); diff --git a/src/domain/user.ts b/src/domain/user.ts index bb38c1b..2dd823d 100644 --- a/src/domain/user.ts +++ b/src/domain/user.ts @@ -1,6 +1,23 @@ // ユーザー import { Snowflake } from "../helpers/id_generator.js"; +export interface UserArgs { + id: Snowflake; + handle: string; + fullHandle: string; + serverID: Snowflake; + nickName: string; + role: number; + bio: string; + headerImageURL: string; + iconImageURL: string; + password: string; + isLocalUser: boolean; + createdAt: Date; + apData: UserAPData; + following: Array; +} + export class User { get id(): Snowflake { return this._id; @@ -70,10 +87,6 @@ export class User { return this._createdAt; } - set createdAt(value: Date) { - this._createdAt = value; - } - get apData(): UserAPData { return this._apData; } @@ -133,28 +146,16 @@ export class User { // フォロー中のユーザー private _following: Set; - constructor(args: { - id: Snowflake; - handle: string; - fullHandle: string; - serverID: Snowflake; - nickName: string; - role: number; - bio: string; - headerImageURL: string; - iconImageURL: string; - password: string; - isLocalUser: boolean; - createdAt: Date; - apData: UserAPData; - following: Array; - }) { + constructor(args: UserArgs) { + this.validate(args); + // 不変 this._id = args.id; this._handle = args.handle; this._fullHandle = args.fullHandle; this._isLocalUser = args.isLocalUser; this._serverID = args.serverID; + this._createdAt = args.createdAt; this._nickName = args.nickName; this._role = args.role; @@ -162,10 +163,36 @@ export class User { this._password = args.password; this._headerImageURL = args.headerImageURL; this._iconImageURL = args.iconImageURL; - this._createdAt = args.createdAt; this._apData = args.apData; this._following = new Set(args.following); } + + private validate(args: UserArgs) { + if ([...args.nickName].length > 64) { + throw new Error("failed to create user: nickname is too long"); + } else if ([...args.nickName].length < 1) { + throw new Error("failed to create user: nickname is too short"); + } + + if ([...args.handle].length > 64) { + throw new Error("failed to create user: handle is too long"); + } else if ([...args.handle].length < 1) { + throw new Error("failed to create user: handle is too short"); + } + + // ホスト名は最小4文字, @で1文字、ハンドルが最小1文字: 合計6文字以上 + if ([...args.fullHandle].length < 6) { + throw new Error("failed to create user: fullHandle is too short"); + } + + if ([...args.bio].length > 3000) { + throw new Error("failed to create user: bio is too long"); + } + + if (args.isLocalUser && args.password.length < 1) { + throw new Error("failed to create user: password is not set"); + } + } } export class UserAPData { diff --git a/src/service/user/create_follow_service.test.ts b/src/service/user/create_follow_service.test.ts index 10afbe5..f949d38 100644 --- a/src/service/user/create_follow_service.test.ts +++ b/src/service/user/create_follow_service.test.ts @@ -9,7 +9,7 @@ describe("create_follow_service", () => { new User({ id: "1" as Snowflake, fullHandle: "testuser@example.com", - password: "", + password: "testUserPassword", role: 0, nickName: "test", handle: "test", @@ -34,7 +34,7 @@ describe("create_follow_service", () => { new User({ id: "2" as Snowflake, fullHandle: "testuser2@example.com", - password: "", + password: "testuser2password", role: 0, nickName: "test", handle: "test",