create-einja-app 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +307 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1041 -0
- package/dist/cli.js.map +1 -0
- package/package.json +62 -0
- package/templates/turborepo-pandacss/.biomeignore +15 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/biome-format.sh +49 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/design-doc-check.sh +61 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/detect-secrets.sh +62 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/large-file-warning.sh +42 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/playwright-resize.sh +36 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/typecheck.sh +37 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/unset-volta-recursion.sh +32 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/validate-git-commit.sh +239 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/warn-index-ts.sh +34 -0
- package/templates/turborepo-pandacss/.claude/hooks/einja/warn-relative-import.sh +48 -0
- package/templates/turborepo-pandacss/.claude/settings.json +174 -0
- package/templates/turborepo-pandacss/.claude/skills/create-einja-app-release/SKILL.md +186 -0
- package/templates/turborepo-pandacss/.claude/skills/dev-cli-release/SKILL.md +173 -0
- package/templates/turborepo-pandacss/.cursor/commands/spec-create.md +227 -0
- package/templates/turborepo-pandacss/.cursor/commands/start-dev.md +98 -0
- package/templates/turborepo-pandacss/.cursor/commands/task-exec.md +287 -0
- package/templates/turborepo-pandacss/.cursor/commands/task-vibe-kanban-loop.md +532 -0
- package/templates/turborepo-pandacss/.cursor/commands/update-docs-by-task-specs.md +448 -0
- package/templates/turborepo-pandacss/.cursor/mcp.json +45 -0
- package/templates/turborepo-pandacss/.cursor/rules/api-rules.mdc +171 -0
- package/templates/turborepo-pandacss/.cursor/rules/api-test-rules.mdc +181 -0
- package/templates/turborepo-pandacss/.cursor/rules/base-code.mdc +70 -0
- package/templates/turborepo-pandacss/.cursor/rules/base-commit-rules.mdc +174 -0
- package/templates/turborepo-pandacss/.cursor/rules/base-design.mdc +12 -0
- package/templates/turborepo-pandacss/.cursor/rules/base-rules.mdc +231 -0
- package/templates/turborepo-pandacss/.cursor/rules/error-handling-rules.mdc +188 -0
- package/templates/turborepo-pandacss/.cursor/rules/refactor-rules.mdc +93 -0
- package/templates/turborepo-pandacss/.dockerignore +126 -0
- package/templates/turborepo-pandacss/.einja-sync.json +35 -0
- package/templates/turborepo-pandacss/.env.ci +25 -0
- package/templates/turborepo-pandacss/.env.example +35 -0
- package/templates/turborepo-pandacss/.env.personal.example +27 -0
- package/templates/turborepo-pandacss/.envrc +4 -0
- package/templates/turborepo-pandacss/.gitattributes +5 -0
- package/templates/turborepo-pandacss/.husky/pre-commit +1 -0
- package/templates/turborepo-pandacss/.lintstagedrc.js +24 -0
- package/templates/turborepo-pandacss/.mcp.json +45 -0
- package/templates/turborepo-pandacss/.node-version +1 -0
- package/templates/turborepo-pandacss/.templateignore +60 -0
- package/templates/turborepo-pandacss/.vscode/extensions.json +3 -0
- package/templates/turborepo-pandacss/CLAUDE.md +415 -0
- package/templates/turborepo-pandacss/README.md +322 -0
- package/templates/turborepo-pandacss/apps/web/middleware.ts +28 -0
- package/templates/turborepo-pandacss/apps/web/next.config.ts +10 -0
- package/templates/turborepo-pandacss/apps/web/package.json +80 -0
- package/templates/turborepo-pandacss/apps/web/panda.config.ts +114 -0
- package/templates/turborepo-pandacss/apps/web/postcss.config.cjs +6 -0
- package/templates/turborepo-pandacss/apps/web/public/file.svg +1 -0
- package/templates/turborepo-pandacss/apps/web/public/globe.svg +1 -0
- package/templates/turborepo-pandacss/apps/web/public/next.svg +1 -0
- package/templates/turborepo-pandacss/apps/web/public/vercel.svg +1 -0
- package/templates/turborepo-pandacss/apps/web/public/window.svg +1 -0
- package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/dashboard/page.tsx +79 -0
- package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/data/_components/UserTable.tsx +203 -0
- package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/data/page.tsx +57 -0
- package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/layout-client.tsx +31 -0
- package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/layout.tsx +17 -0
- package/templates/turborepo-pandacss/apps/web/src/app/(authenticated)/profile/page.tsx +59 -0
- package/templates/turborepo-pandacss/apps/web/src/app/api/auth/[...nextauth]/route.ts +3 -0
- package/templates/turborepo-pandacss/apps/web/src/app/api/auth/signup/route.ts +70 -0
- package/templates/turborepo-pandacss/apps/web/src/app/error.tsx +106 -0
- package/templates/turborepo-pandacss/apps/web/src/app/favicon.ico +0 -0
- package/templates/turborepo-pandacss/apps/web/src/app/global-error.tsx +110 -0
- package/templates/turborepo-pandacss/apps/web/src/app/globals.css +121 -0
- package/templates/turborepo-pandacss/apps/web/src/app/layout.tsx +28 -0
- package/templates/turborepo-pandacss/apps/web/src/app/not-found.tsx +54 -0
- package/templates/turborepo-pandacss/apps/web/src/app/page.module.css +165 -0
- package/templates/turborepo-pandacss/apps/web/src/app/page.test.tsx +52 -0
- package/templates/turborepo-pandacss/apps/web/src/app/page.tsx +284 -0
- package/templates/turborepo-pandacss/apps/web/src/app/signin/page.tsx +296 -0
- package/templates/turborepo-pandacss/apps/web/src/app/signup/page.tsx +395 -0
- package/templates/turborepo-pandacss/apps/web/src/application/use-cases/UserUseCases.test.ts +229 -0
- package/templates/turborepo-pandacss/apps/web/src/application/use-cases/UserUseCases.ts +115 -0
- package/templates/turborepo-pandacss/apps/web/src/components/auth/login-button.tsx +35 -0
- package/templates/turborepo-pandacss/apps/web/src/components/auth/logout-button.tsx +24 -0
- package/templates/turborepo-pandacss/apps/web/src/components/auth/user-avatar.test.tsx +68 -0
- package/templates/turborepo-pandacss/apps/web/src/components/auth/user-avatar.tsx +43 -0
- package/templates/turborepo-pandacss/apps/web/src/components/dashboard/dashboard-stats.tsx +128 -0
- package/templates/turborepo-pandacss/apps/web/src/components/providers/query-provider.tsx +30 -0
- package/templates/turborepo-pandacss/apps/web/src/components/providers/session-provider.tsx +12 -0
- package/templates/turborepo-pandacss/apps/web/src/components/shared/Sidebar.tsx +175 -0
- package/templates/turborepo-pandacss/apps/web/src/components/shared/header.tsx +166 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/accordion.tsx +64 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/alert-dialog.tsx +135 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/alert.tsx +60 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/aspect-ratio.tsx +9 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/avatar.tsx +41 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/badge.tsx +39 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/breadcrumb.tsx +101 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/button.tsx +56 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/card.tsx +75 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/checkbox.tsx +29 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/data-table.tsx +189 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/dialog-hook.tsx +210 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/dialog.tsx +129 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/drawer.tsx +124 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/dropdown-menu.tsx +228 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/form.tsx +152 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/hover-card.tsx +38 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/input.tsx +21 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/label.tsx +21 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/pagination.tsx +105 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/popover.tsx +42 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/progress.tsx +28 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/select.tsx +170 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/separator.tsx +28 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/skeleton.tsx +13 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/sonner.tsx +25 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/table.tsx +92 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/tabs.tsx +54 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/textarea.tsx +18 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/tooltip.tsx +57 -0
- package/templates/turborepo-pandacss/apps/web/src/components/ui/typography.tsx +158 -0
- package/templates/turborepo-pandacss/apps/web/src/lib/auth/guard.ts +36 -0
- package/templates/turborepo-pandacss/apps/web/src/lib/auth/index.ts +22 -0
- package/templates/turborepo-pandacss/apps/web/src/lib/prisma.ts +3 -0
- package/templates/turborepo-pandacss/apps/web/src/lib/utils.ts +6 -0
- package/templates/turborepo-pandacss/apps/web/test/globals.d.ts +1 -0
- package/templates/turborepo-pandacss/apps/web/test/matchers.d.ts +1 -0
- package/templates/turborepo-pandacss/apps/web/test/setup.ts +22 -0
- package/templates/turborepo-pandacss/apps/web/tsconfig.json +37 -0
- package/templates/turborepo-pandacss/apps/web/vitest.config.ts +20 -0
- package/templates/turborepo-pandacss/apps/web/vitest.d.ts +2 -0
- package/templates/turborepo-pandacss/biome.json +60 -0
- package/templates/turborepo-pandacss/components.json +21 -0
- package/templates/turborepo-pandacss/docker-compose.yml +27 -0
- package/templates/turborepo-pandacss/middleware.ts +32 -0
- package/templates/turborepo-pandacss/next.config.ts +10 -0
- package/templates/turborepo-pandacss/package-lock.json +9346 -0
- package/templates/turborepo-pandacss/package.json +64 -0
- package/templates/turborepo-pandacss/packages/config/package.json +41 -0
- package/templates/turborepo-pandacss/packages/config/panda.config.ts +114 -0
- package/templates/turborepo-pandacss/packages/config/src/index.ts +24 -0
- package/templates/turborepo-pandacss/packages/config/src/worktree-config-loader.ts +129 -0
- package/templates/turborepo-pandacss/packages/config/src/worktree-config.ts +75 -0
- package/templates/turborepo-pandacss/packages/config/tsconfig.build.json +19 -0
- package/templates/turborepo-pandacss/packages/config/tsconfig.json +24 -0
- package/templates/turborepo-pandacss/packages/config/typescript/base.json +19 -0
- package/templates/turborepo-pandacss/packages/front-core/package.json +24 -0
- package/templates/turborepo-pandacss/packages/front-core/src/auth/config.ts +84 -0
- package/templates/turborepo-pandacss/packages/front-core/src/auth/index.ts +5 -0
- package/templates/turborepo-pandacss/packages/front-core/src/auth/types/next-auth.d.ts +20 -0
- package/templates/turborepo-pandacss/packages/front-core/src/auth/utils.ts +29 -0
- package/templates/turborepo-pandacss/packages/front-core/src/context/index.ts +2 -0
- package/templates/turborepo-pandacss/packages/front-core/src/hooks/index.ts +2 -0
- package/templates/turborepo-pandacss/packages/front-core/src/index.ts +4 -0
- package/templates/turborepo-pandacss/packages/front-core/src/utils/index.ts +2 -0
- package/templates/turborepo-pandacss/packages/front-core/tsconfig.json +14 -0
- package/templates/turborepo-pandacss/packages/server-core/package.json +32 -0
- package/templates/turborepo-pandacss/packages/server-core/prisma/schema.prisma +102 -0
- package/templates/turborepo-pandacss/packages/server-core/prisma/seed.ts +67 -0
- package/templates/turborepo-pandacss/packages/server-core/prisma.config.ts +8 -0
- package/templates/turborepo-pandacss/packages/server-core/src/__generated__/fabbrica/index.d.ts +270 -0
- package/templates/turborepo-pandacss/packages/server-core/src/__generated__/fabbrica/index.js +484 -0
- package/templates/turborepo-pandacss/packages/server-core/src/core/result.test.ts +78 -0
- package/templates/turborepo-pandacss/packages/server-core/src/core/result.ts +53 -0
- package/templates/turborepo-pandacss/packages/server-core/src/domain/.gitkeep +0 -0
- package/templates/turborepo-pandacss/packages/server-core/src/domain/entities/User.test.ts +232 -0
- package/templates/turborepo-pandacss/packages/server-core/src/domain/entities/User.ts +105 -0
- package/templates/turborepo-pandacss/packages/server-core/src/domain/repository-interfaces/IUserRepository.ts +101 -0
- package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/client.ts +15 -0
- package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/mappers/UserMapper.test.ts +278 -0
- package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/mappers/UserMapper.ts +103 -0
- package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/repositories/UserRepository.test.ts +317 -0
- package/templates/turborepo-pandacss/packages/server-core/src/infrastructure/database/repositories/UserRepository.ts +169 -0
- package/templates/turborepo-pandacss/packages/server-core/src/testing/factories/index.ts +22 -0
- package/templates/turborepo-pandacss/packages/server-core/src/testing/factories/user.factory.ts +123 -0
- package/templates/turborepo-pandacss/packages/server-core/src/testing/fixtures/users.ts +92 -0
- package/templates/turborepo-pandacss/packages/server-core/src/testing/helpers/date.ts +49 -0
- package/templates/turborepo-pandacss/packages/server-core/src/testing/helpers/password.ts +50 -0
- package/templates/turborepo-pandacss/packages/server-core/src/testing/index.ts +26 -0
- package/templates/turborepo-pandacss/packages/server-core/tsconfig.json +14 -0
- package/templates/turborepo-pandacss/packages/server-core/vitest.config.ts +15 -0
- package/templates/turborepo-pandacss/packages/ui/package.json +37 -0
- package/templates/turborepo-pandacss/packages/ui/src/accordion.tsx +66 -0
- package/templates/turborepo-pandacss/packages/ui/src/alert-dialog.tsx +157 -0
- package/templates/turborepo-pandacss/packages/ui/src/alert.tsx +66 -0
- package/templates/turborepo-pandacss/packages/ui/src/aspect-ratio.tsx +11 -0
- package/templates/turborepo-pandacss/packages/ui/src/avatar.tsx +53 -0
- package/templates/turborepo-pandacss/packages/ui/src/badge.tsx +46 -0
- package/templates/turborepo-pandacss/packages/ui/src/breadcrumb.tsx +108 -0
- package/templates/turborepo-pandacss/packages/ui/src/button.tsx +59 -0
- package/templates/turborepo-pandacss/packages/ui/src/card.tsx +92 -0
- package/templates/turborepo-pandacss/packages/ui/src/checkbox.tsx +32 -0
- package/templates/turborepo-pandacss/packages/ui/src/data-table.tsx +216 -0
- package/templates/turborepo-pandacss/packages/ui/src/dialog-hook.tsx +226 -0
- package/templates/turborepo-pandacss/packages/ui/src/dialog.tsx +143 -0
- package/templates/turborepo-pandacss/packages/ui/src/drawer.tsx +135 -0
- package/templates/turborepo-pandacss/packages/ui/src/dropdown-menu.tsx +257 -0
- package/templates/turborepo-pandacss/packages/ui/src/form.tsx +168 -0
- package/templates/turborepo-pandacss/packages/ui/src/hover-card.tsx +44 -0
- package/templates/turborepo-pandacss/packages/ui/src/input.tsx +21 -0
- package/templates/turborepo-pandacss/packages/ui/src/label.tsx +24 -0
- package/templates/turborepo-pandacss/packages/ui/src/lib/utils.ts +6 -0
- package/templates/turborepo-pandacss/packages/ui/src/pagination.tsx +126 -0
- package/templates/turborepo-pandacss/packages/ui/src/popover.tsx +48 -0
- package/templates/turborepo-pandacss/packages/ui/src/progress.tsx +31 -0
- package/templates/turborepo-pandacss/packages/ui/src/select.tsx +185 -0
- package/templates/turborepo-pandacss/packages/ui/src/separator.tsx +28 -0
- package/templates/turborepo-pandacss/packages/ui/src/skeleton.tsx +13 -0
- package/templates/turborepo-pandacss/packages/ui/src/sonner.tsx +25 -0
- package/templates/turborepo-pandacss/packages/ui/src/table.tsx +116 -0
- package/templates/turborepo-pandacss/packages/ui/src/tabs.tsx +66 -0
- package/templates/turborepo-pandacss/packages/ui/src/textarea.tsx +18 -0
- package/templates/turborepo-pandacss/packages/ui/src/tooltip.tsx +61 -0
- package/templates/turborepo-pandacss/packages/ui/src/typography.tsx +187 -0
- package/templates/turborepo-pandacss/packages/ui/tsconfig.json +20 -0
- package/templates/turborepo-pandacss/panda.config.ts +114 -0
- package/templates/turborepo-pandacss/pnpm-lock.yaml +9032 -0
- package/templates/turborepo-pandacss/pnpm-workspace.yaml +11 -0
- package/templates/turborepo-pandacss/postcss.config.cjs +6 -0
- package/templates/turborepo-pandacss/prisma/schema.prisma +82 -0
- package/templates/turborepo-pandacss/public/file.svg +1 -0
- package/templates/turborepo-pandacss/public/globe.svg +1 -0
- package/templates/turborepo-pandacss/public/next.svg +1 -0
- package/templates/turborepo-pandacss/public/vercel.svg +1 -0
- package/templates/turborepo-pandacss/public/window.svg +1 -0
- package/templates/turborepo-pandacss/scripts/cli-template-update.ts +387 -0
- package/templates/turborepo-pandacss/scripts/env-show.ts +129 -0
- package/templates/turborepo-pandacss/scripts/env.ts +555 -0
- package/templates/turborepo-pandacss/scripts/init.sh +92 -0
- package/templates/turborepo-pandacss/scripts/setup-dev.ts +640 -0
- package/templates/turborepo-pandacss/scripts/template-update.ts +277 -0
- package/templates/turborepo-pandacss/scripts/worktree/dev.ts +872 -0
- package/templates/turborepo-pandacss/test/globals.d.ts +1 -0
- package/templates/turborepo-pandacss/test/setup.ts +22 -0
- package/templates/turborepo-pandacss/tsconfig.json +46 -0
- package/templates/turborepo-pandacss/turbo.json +57 -0
- package/templates/turborepo-pandacss/vitest.config.ts +20 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import type { User as PrismaUser, UserRole as PrismaUserRole, UserStatus as PrismaUserStatus } from "@prisma/client";
|
|
2
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
3
|
+
import { User } from "../../../domain/entities/User";
|
|
4
|
+
import { UserFactory, initialize } from "../../../testing";
|
|
5
|
+
import { UserMapper } from "./UserMapper";
|
|
6
|
+
|
|
7
|
+
describe("UserMapper", () => {
|
|
8
|
+
beforeAll(() => {
|
|
9
|
+
// マッパーテストではPrismaクライアントは使用しないため、空のオブジェクトを渡す
|
|
10
|
+
// biome-ignore lint/suspicious/noExplicitAny: test fixture initialization
|
|
11
|
+
initialize({ prisma: {} as any });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe("toDomain", () => {
|
|
15
|
+
it("PrismaUserをDomain Userに変換できる", async () => {
|
|
16
|
+
// Given
|
|
17
|
+
const prismaUser = await UserFactory.build();
|
|
18
|
+
|
|
19
|
+
// When
|
|
20
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
21
|
+
const domainUser = UserMapper.toDomain(prismaUser as any);
|
|
22
|
+
|
|
23
|
+
// Then
|
|
24
|
+
expect(domainUser).toBeInstanceOf(User);
|
|
25
|
+
expect(domainUser.id).toBe(prismaUser.id);
|
|
26
|
+
expect(domainUser.email).toBe(prismaUser.email);
|
|
27
|
+
expect(domainUser.name).toBe(prismaUser.name);
|
|
28
|
+
expect(domainUser.status).toBe(prismaUser.status);
|
|
29
|
+
expect(domainUser.role).toBe(prismaUser.role);
|
|
30
|
+
expect(domainUser.createdAt).toEqual(prismaUser.createdAt);
|
|
31
|
+
expect(domainUser.lastLogin).toEqual(prismaUser.lastLogin);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("nameがnullでも変換できる", async () => {
|
|
35
|
+
// Given
|
|
36
|
+
const prismaUser = await UserFactory.build({ name: null });
|
|
37
|
+
|
|
38
|
+
// When
|
|
39
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
40
|
+
const domainUser = UserMapper.toDomain(prismaUser as any);
|
|
41
|
+
|
|
42
|
+
// Then
|
|
43
|
+
expect(domainUser.name).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("lastLoginがnullでも変換できる", async () => {
|
|
47
|
+
// Given
|
|
48
|
+
const prismaUser = await UserFactory.build({ lastLogin: null });
|
|
49
|
+
|
|
50
|
+
// When
|
|
51
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
52
|
+
const domainUser = UserMapper.toDomain(prismaUser as any);
|
|
53
|
+
|
|
54
|
+
// Then
|
|
55
|
+
expect(domainUser.lastLogin).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("status変換", () => {
|
|
59
|
+
it("activeを変換できる", async () => {
|
|
60
|
+
// Given
|
|
61
|
+
const prismaUser = await UserFactory.build({ status: "active" as PrismaUserStatus });
|
|
62
|
+
|
|
63
|
+
// When
|
|
64
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
65
|
+
const domainUser = UserMapper.toDomain(prismaUser as any);
|
|
66
|
+
|
|
67
|
+
// Then
|
|
68
|
+
expect(domainUser.status).toBe("active");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("inactiveを変換できる", async () => {
|
|
72
|
+
// Given
|
|
73
|
+
const prismaUser = await UserFactory.build({ status: "inactive" as PrismaUserStatus });
|
|
74
|
+
|
|
75
|
+
// When
|
|
76
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
77
|
+
const domainUser = UserMapper.toDomain(prismaUser as any);
|
|
78
|
+
|
|
79
|
+
// Then
|
|
80
|
+
expect(domainUser.status).toBe("inactive");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("pendingを変換できる", async () => {
|
|
84
|
+
// Given
|
|
85
|
+
const prismaUser = await UserFactory.build({ status: "pending" as PrismaUserStatus });
|
|
86
|
+
|
|
87
|
+
// When
|
|
88
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
89
|
+
const domainUser = UserMapper.toDomain(prismaUser as any);
|
|
90
|
+
|
|
91
|
+
// Then
|
|
92
|
+
expect(domainUser.status).toBe("pending");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("role変換", () => {
|
|
97
|
+
it("adminを変換できる", async () => {
|
|
98
|
+
// Given
|
|
99
|
+
const prismaUser = await UserFactory.build({ role: "admin" as PrismaUserRole });
|
|
100
|
+
|
|
101
|
+
// When
|
|
102
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
103
|
+
const domainUser = UserMapper.toDomain(prismaUser as any);
|
|
104
|
+
|
|
105
|
+
// Then
|
|
106
|
+
expect(domainUser.role).toBe("admin");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("userを変換できる", async () => {
|
|
110
|
+
// Given
|
|
111
|
+
const prismaUser = await UserFactory.build({ role: "user" as PrismaUserRole });
|
|
112
|
+
|
|
113
|
+
// When
|
|
114
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
115
|
+
const domainUser = UserMapper.toDomain(prismaUser as any);
|
|
116
|
+
|
|
117
|
+
// Then
|
|
118
|
+
expect(domainUser.role).toBe("user");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("moderatorを変換できる", async () => {
|
|
122
|
+
// Given
|
|
123
|
+
const prismaUser = await UserFactory.build({ role: "moderator" as PrismaUserRole });
|
|
124
|
+
|
|
125
|
+
// When
|
|
126
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
127
|
+
const domainUser = UserMapper.toDomain(prismaUser as any);
|
|
128
|
+
|
|
129
|
+
// Then
|
|
130
|
+
expect(domainUser.role).toBe("moderator");
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("toPrismaUpdate", () => {
|
|
136
|
+
it("Domain UserをPrisma更新データに変換できる", async () => {
|
|
137
|
+
// Given
|
|
138
|
+
const props = await UserFactory.build({
|
|
139
|
+
name: "Updated Name",
|
|
140
|
+
status: "active" as PrismaUserStatus,
|
|
141
|
+
role: "admin" as PrismaUserRole,
|
|
142
|
+
lastLogin: new Date("2025-01-03T00:00:00Z"),
|
|
143
|
+
});
|
|
144
|
+
const domainUser = new User(props);
|
|
145
|
+
|
|
146
|
+
// When
|
|
147
|
+
const prismaData = UserMapper.toPrismaUpdate(domainUser);
|
|
148
|
+
|
|
149
|
+
// Then
|
|
150
|
+
expect(prismaData.name).toBe("Updated Name");
|
|
151
|
+
expect(prismaData.status).toBe("active");
|
|
152
|
+
expect(prismaData.role).toBe("admin");
|
|
153
|
+
expect(prismaData.lastLogin).toEqual(new Date("2025-01-03T00:00:00Z"));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("nameがnullでも変換できる", async () => {
|
|
157
|
+
// Given
|
|
158
|
+
const props = await UserFactory.build({
|
|
159
|
+
name: null,
|
|
160
|
+
status: "active" as PrismaUserStatus,
|
|
161
|
+
role: "user" as PrismaUserRole,
|
|
162
|
+
lastLogin: null,
|
|
163
|
+
});
|
|
164
|
+
const domainUser = new User(props);
|
|
165
|
+
|
|
166
|
+
// When
|
|
167
|
+
const prismaData = UserMapper.toPrismaUpdate(domainUser);
|
|
168
|
+
|
|
169
|
+
// Then
|
|
170
|
+
expect(prismaData.name).toBeNull();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("lastLoginがnullでも変換できる", async () => {
|
|
174
|
+
// Given
|
|
175
|
+
const props = await UserFactory.build({
|
|
176
|
+
name: "Test",
|
|
177
|
+
status: "pending" as PrismaUserStatus,
|
|
178
|
+
role: "user" as PrismaUserRole,
|
|
179
|
+
lastLogin: null,
|
|
180
|
+
});
|
|
181
|
+
const domainUser = new User(props);
|
|
182
|
+
|
|
183
|
+
// When
|
|
184
|
+
const prismaData = UserMapper.toPrismaUpdate(domainUser);
|
|
185
|
+
|
|
186
|
+
// Then
|
|
187
|
+
expect(prismaData.lastLogin).toBeNull();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("status逆変換", () => {
|
|
191
|
+
it("activeをPrisma形式に変換できる", async () => {
|
|
192
|
+
// Given
|
|
193
|
+
const props = await UserFactory.build({
|
|
194
|
+
status: "active" as PrismaUserStatus,
|
|
195
|
+
});
|
|
196
|
+
const domainUser = new User(props);
|
|
197
|
+
|
|
198
|
+
// When
|
|
199
|
+
const prismaData = UserMapper.toPrismaUpdate(domainUser);
|
|
200
|
+
|
|
201
|
+
// Then
|
|
202
|
+
expect(prismaData.status).toBe("active");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("inactiveをPrisma形式に変換できる", async () => {
|
|
206
|
+
// Given
|
|
207
|
+
const props = await UserFactory.build({
|
|
208
|
+
status: "inactive" as PrismaUserStatus,
|
|
209
|
+
});
|
|
210
|
+
const domainUser = new User(props);
|
|
211
|
+
|
|
212
|
+
// When
|
|
213
|
+
const prismaData = UserMapper.toPrismaUpdate(domainUser);
|
|
214
|
+
|
|
215
|
+
// Then
|
|
216
|
+
expect(prismaData.status).toBe("inactive");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("pendingをPrisma形式に変換できる", async () => {
|
|
220
|
+
// Given
|
|
221
|
+
const props = await UserFactory.build({
|
|
222
|
+
status: "pending" as PrismaUserStatus,
|
|
223
|
+
});
|
|
224
|
+
const domainUser = new User(props);
|
|
225
|
+
|
|
226
|
+
// When
|
|
227
|
+
const prismaData = UserMapper.toPrismaUpdate(domainUser);
|
|
228
|
+
|
|
229
|
+
// Then
|
|
230
|
+
expect(prismaData.status).toBe("pending");
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe("role逆変換", () => {
|
|
235
|
+
it("adminをPrisma形式に変換できる", async () => {
|
|
236
|
+
// Given
|
|
237
|
+
const props = await UserFactory.build({
|
|
238
|
+
role: "admin" as PrismaUserRole,
|
|
239
|
+
});
|
|
240
|
+
const domainUser = new User(props);
|
|
241
|
+
|
|
242
|
+
// When
|
|
243
|
+
const prismaData = UserMapper.toPrismaUpdate(domainUser);
|
|
244
|
+
|
|
245
|
+
// Then
|
|
246
|
+
expect(prismaData.role).toBe("admin");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("userをPrisma形式に変換できる", async () => {
|
|
250
|
+
// Given
|
|
251
|
+
const props = await UserFactory.build({
|
|
252
|
+
role: "user" as PrismaUserRole,
|
|
253
|
+
});
|
|
254
|
+
const domainUser = new User(props);
|
|
255
|
+
|
|
256
|
+
// When
|
|
257
|
+
const prismaData = UserMapper.toPrismaUpdate(domainUser);
|
|
258
|
+
|
|
259
|
+
// Then
|
|
260
|
+
expect(prismaData.role).toBe("user");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("moderatorをPrisma形式に変換できる", async () => {
|
|
264
|
+
// Given
|
|
265
|
+
const props = await UserFactory.build({
|
|
266
|
+
role: "moderator" as PrismaUserRole,
|
|
267
|
+
});
|
|
268
|
+
const domainUser = new User(props);
|
|
269
|
+
|
|
270
|
+
// When
|
|
271
|
+
const prismaData = UserMapper.toPrismaUpdate(domainUser);
|
|
272
|
+
|
|
273
|
+
// Then
|
|
274
|
+
expect(prismaData.role).toBe("moderator");
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UserMapper
|
|
3
|
+
*
|
|
4
|
+
* Prismaモデル ⇔ ドメインエンティティの変換を担当。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { User as PrismaUser, UserRole as PrismaUserRole, UserStatus as PrismaUserStatus } from "@prisma/client";
|
|
8
|
+
import { User, type UserRole, type UserStatus } from "../../../domain/entities/User";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Prismaのステータスをドメインのステータスに変換
|
|
12
|
+
*/
|
|
13
|
+
function mapPrismaStatusToDomain(status: PrismaUserStatus): UserStatus {
|
|
14
|
+
switch (status) {
|
|
15
|
+
case "active":
|
|
16
|
+
return "active";
|
|
17
|
+
case "inactive":
|
|
18
|
+
return "inactive";
|
|
19
|
+
case "pending":
|
|
20
|
+
return "pending";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Prismaのロールをドメインのロールに変換
|
|
26
|
+
*/
|
|
27
|
+
function mapPrismaRoleToDomain(role: PrismaUserRole): UserRole {
|
|
28
|
+
switch (role) {
|
|
29
|
+
case "admin":
|
|
30
|
+
return "admin";
|
|
31
|
+
case "user":
|
|
32
|
+
return "user";
|
|
33
|
+
case "moderator":
|
|
34
|
+
return "moderator";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* ドメインのステータスをPrismaのステータスに変換
|
|
40
|
+
*/
|
|
41
|
+
function mapDomainStatusToPrisma(status: UserStatus): PrismaUserStatus {
|
|
42
|
+
switch (status) {
|
|
43
|
+
case "active":
|
|
44
|
+
return "active";
|
|
45
|
+
case "inactive":
|
|
46
|
+
return "inactive";
|
|
47
|
+
case "pending":
|
|
48
|
+
return "pending";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* ドメインのロールをPrismaのロールに変換
|
|
54
|
+
*/
|
|
55
|
+
function mapDomainRoleToPrisma(role: UserRole): PrismaUserRole {
|
|
56
|
+
switch (role) {
|
|
57
|
+
case "admin":
|
|
58
|
+
return "admin";
|
|
59
|
+
case "user":
|
|
60
|
+
return "user";
|
|
61
|
+
case "moderator":
|
|
62
|
+
return "moderator";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* UserMapper
|
|
68
|
+
*
|
|
69
|
+
* Prisma ⇔ Domain の変換を行うマッパークラス
|
|
70
|
+
*/
|
|
71
|
+
export const UserMapper = {
|
|
72
|
+
/**
|
|
73
|
+
* PrismaのUserをドメインのUserに変換
|
|
74
|
+
*/
|
|
75
|
+
toDomain(prismaUser: PrismaUser): User {
|
|
76
|
+
return new User({
|
|
77
|
+
id: prismaUser.id,
|
|
78
|
+
email: prismaUser.email,
|
|
79
|
+
name: prismaUser.name,
|
|
80
|
+
status: mapPrismaStatusToDomain(prismaUser.status),
|
|
81
|
+
role: mapPrismaRoleToDomain(prismaUser.role),
|
|
82
|
+
createdAt: prismaUser.createdAt,
|
|
83
|
+
lastLogin: prismaUser.lastLogin,
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* ドメインのUserをPrismaの更新用データに変換
|
|
89
|
+
*/
|
|
90
|
+
toPrismaUpdate(user: User): {
|
|
91
|
+
name: string | null;
|
|
92
|
+
status: PrismaUserStatus;
|
|
93
|
+
role: PrismaUserRole;
|
|
94
|
+
lastLogin: Date | null;
|
|
95
|
+
} {
|
|
96
|
+
return {
|
|
97
|
+
name: user.name,
|
|
98
|
+
status: mapDomainStatusToPrisma(user.status),
|
|
99
|
+
role: mapDomainRoleToPrisma(user.role),
|
|
100
|
+
lastLogin: user.lastLogin,
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
};
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import type { User as PrismaUser, UserRole as PrismaUserRole, UserStatus as PrismaUserStatus } from "@prisma/client";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { isFailure, isSuccess } from "../../../core/result";
|
|
4
|
+
import { UserFactory, initialize } from "../../../testing";
|
|
5
|
+
|
|
6
|
+
// Prismaクライアントをモック
|
|
7
|
+
vi.mock("../client", () => ({
|
|
8
|
+
prisma: {
|
|
9
|
+
user: {
|
|
10
|
+
findMany: vi.fn(),
|
|
11
|
+
findFirst: vi.fn(),
|
|
12
|
+
findUnique: vi.fn(),
|
|
13
|
+
count: vi.fn(),
|
|
14
|
+
update: vi.fn(),
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
// モックしたprismaをインポート
|
|
20
|
+
import { prisma } from "../client";
|
|
21
|
+
import { userRepository } from "./UserRepository";
|
|
22
|
+
|
|
23
|
+
describe("UserRepository", () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
// リポジトリテストではモックprismaを使用するため、空のオブジェクトを渡す
|
|
26
|
+
// biome-ignore lint/suspicious/noExplicitAny: test fixture initialization
|
|
27
|
+
initialize({ prisma: {} as any });
|
|
28
|
+
vi.clearAllMocks();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("search", () => {
|
|
32
|
+
it("ページネーション付きでユーザー一覧を取得できる", async () => {
|
|
33
|
+
// Given
|
|
34
|
+
const mockUsers = [
|
|
35
|
+
await UserFactory.build({ id: "user-1", email: "user1@example.com" }),
|
|
36
|
+
await UserFactory.build({ id: "user-2", email: "user2@example.com" }),
|
|
37
|
+
];
|
|
38
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
39
|
+
vi.mocked(prisma.user.findMany).mockResolvedValue(mockUsers as any);
|
|
40
|
+
vi.mocked(prisma.user.count).mockResolvedValue(25);
|
|
41
|
+
|
|
42
|
+
// When
|
|
43
|
+
const result = await userRepository.search({}, { page: 2, limit: 10 });
|
|
44
|
+
|
|
45
|
+
// Then
|
|
46
|
+
expect(isSuccess(result)).toBe(true);
|
|
47
|
+
if (isSuccess(result)) {
|
|
48
|
+
expect(result.value.items).toHaveLength(2);
|
|
49
|
+
expect(result.value.total).toBe(25);
|
|
50
|
+
expect(result.value.page).toBe(2);
|
|
51
|
+
expect(result.value.limit).toBe(10);
|
|
52
|
+
expect(result.value.totalPages).toBe(3);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("検索条件なしで全件取得できる", async () => {
|
|
57
|
+
// Given
|
|
58
|
+
const mockUsers = [await UserFactory.build()];
|
|
59
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
60
|
+
vi.mocked(prisma.user.findMany).mockResolvedValue(mockUsers as any);
|
|
61
|
+
vi.mocked(prisma.user.count).mockResolvedValue(1);
|
|
62
|
+
|
|
63
|
+
// When
|
|
64
|
+
const result = await userRepository.search({});
|
|
65
|
+
|
|
66
|
+
// Then
|
|
67
|
+
expect(isSuccess(result)).toBe(true);
|
|
68
|
+
if (isSuccess(result)) {
|
|
69
|
+
expect(result.value.items).toHaveLength(1);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("statusで絞り込みできる", async () => {
|
|
74
|
+
// Given
|
|
75
|
+
vi.mocked(prisma.user.findMany).mockResolvedValue([]);
|
|
76
|
+
vi.mocked(prisma.user.count).mockResolvedValue(0);
|
|
77
|
+
|
|
78
|
+
// When
|
|
79
|
+
await userRepository.search({ status: "active" });
|
|
80
|
+
|
|
81
|
+
// Then
|
|
82
|
+
expect(prisma.user.findMany).toHaveBeenCalledWith(
|
|
83
|
+
expect.objectContaining({
|
|
84
|
+
where: expect.objectContaining({ status: "active" }),
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("roleで絞り込みできる", async () => {
|
|
90
|
+
// Given
|
|
91
|
+
vi.mocked(prisma.user.findMany).mockResolvedValue([]);
|
|
92
|
+
vi.mocked(prisma.user.count).mockResolvedValue(0);
|
|
93
|
+
|
|
94
|
+
// When
|
|
95
|
+
await userRepository.search({ role: "admin" });
|
|
96
|
+
|
|
97
|
+
// Then
|
|
98
|
+
expect(prisma.user.findMany).toHaveBeenCalledWith(
|
|
99
|
+
expect.objectContaining({
|
|
100
|
+
where: expect.objectContaining({ role: "admin" }),
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("検索テキストで名前・メールを絞り込みできる", async () => {
|
|
106
|
+
// Given
|
|
107
|
+
vi.mocked(prisma.user.findMany).mockResolvedValue([]);
|
|
108
|
+
vi.mocked(prisma.user.count).mockResolvedValue(0);
|
|
109
|
+
|
|
110
|
+
// When
|
|
111
|
+
await userRepository.search({ search: "john" });
|
|
112
|
+
|
|
113
|
+
// Then
|
|
114
|
+
expect(prisma.user.findMany).toHaveBeenCalledWith(
|
|
115
|
+
expect.objectContaining({
|
|
116
|
+
where: expect.objectContaining({
|
|
117
|
+
OR: [
|
|
118
|
+
{ name: { contains: "john", mode: "insensitive" } },
|
|
119
|
+
{ email: { contains: "john", mode: "insensitive" } },
|
|
120
|
+
],
|
|
121
|
+
}),
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("空の検索結果でも成功を返す", async () => {
|
|
127
|
+
// Given
|
|
128
|
+
vi.mocked(prisma.user.findMany).mockResolvedValue([]);
|
|
129
|
+
vi.mocked(prisma.user.count).mockResolvedValue(0);
|
|
130
|
+
|
|
131
|
+
// When
|
|
132
|
+
const result = await userRepository.search({});
|
|
133
|
+
|
|
134
|
+
// Then
|
|
135
|
+
expect(isSuccess(result)).toBe(true);
|
|
136
|
+
if (isSuccess(result)) {
|
|
137
|
+
expect(result.value.items).toHaveLength(0);
|
|
138
|
+
expect(result.value.total).toBe(0);
|
|
139
|
+
expect(result.value.totalPages).toBe(0);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("ページネーションのデフォルト値が適用される", async () => {
|
|
144
|
+
// Given
|
|
145
|
+
vi.mocked(prisma.user.findMany).mockResolvedValue([]);
|
|
146
|
+
vi.mocked(prisma.user.count).mockResolvedValue(0);
|
|
147
|
+
|
|
148
|
+
// When
|
|
149
|
+
const result = await userRepository.search({});
|
|
150
|
+
|
|
151
|
+
// Then
|
|
152
|
+
expect(prisma.user.findMany).toHaveBeenCalledWith(
|
|
153
|
+
expect.objectContaining({
|
|
154
|
+
skip: 0,
|
|
155
|
+
take: 10,
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
158
|
+
if (isSuccess(result)) {
|
|
159
|
+
expect(result.value.page).toBe(1);
|
|
160
|
+
expect(result.value.limit).toBe(10);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("DBエラー時にfailure Resultを返す", async () => {
|
|
165
|
+
// Given
|
|
166
|
+
vi.mocked(prisma.user.findMany).mockRejectedValue(new Error("DB connection failed"));
|
|
167
|
+
|
|
168
|
+
// When
|
|
169
|
+
const result = await userRepository.search({});
|
|
170
|
+
|
|
171
|
+
// Then
|
|
172
|
+
expect(isFailure(result)).toBe(true);
|
|
173
|
+
if (isFailure(result)) {
|
|
174
|
+
expect(result.error.message).toBe("DB connection failed");
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("find", () => {
|
|
180
|
+
it("条件に一致するユーザーを取得できる", async () => {
|
|
181
|
+
// Given
|
|
182
|
+
const mockUser = await UserFactory.build();
|
|
183
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
184
|
+
vi.mocked(prisma.user.findFirst).mockResolvedValue(mockUser as any);
|
|
185
|
+
|
|
186
|
+
// When
|
|
187
|
+
const result = await userRepository.find({ email: "test@example.com" });
|
|
188
|
+
|
|
189
|
+
// Then
|
|
190
|
+
expect(isSuccess(result)).toBe(true);
|
|
191
|
+
if (isSuccess(result)) {
|
|
192
|
+
expect(result.value).not.toBeNull();
|
|
193
|
+
expect(result.value?.email).toBeDefined();
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("見つからない場合はnullを返す", async () => {
|
|
198
|
+
// Given
|
|
199
|
+
vi.mocked(prisma.user.findFirst).mockResolvedValue(null);
|
|
200
|
+
|
|
201
|
+
// When
|
|
202
|
+
const result = await userRepository.find({ email: "notfound@example.com" });
|
|
203
|
+
|
|
204
|
+
// Then
|
|
205
|
+
expect(isSuccess(result)).toBe(true);
|
|
206
|
+
if (isSuccess(result)) {
|
|
207
|
+
expect(result.value).toBeNull();
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("DBエラー時にfailure Resultを返す", async () => {
|
|
212
|
+
// Given
|
|
213
|
+
vi.mocked(prisma.user.findFirst).mockRejectedValue(new Error("Query failed"));
|
|
214
|
+
|
|
215
|
+
// When
|
|
216
|
+
const result = await userRepository.find({ email: "test@example.com" });
|
|
217
|
+
|
|
218
|
+
// Then
|
|
219
|
+
expect(isFailure(result)).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe("findById", () => {
|
|
224
|
+
it("IDでユーザーを取得できる", async () => {
|
|
225
|
+
// Given
|
|
226
|
+
const mockUser = await UserFactory.build({ id: "user-456" });
|
|
227
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
228
|
+
vi.mocked(prisma.user.findUnique).mockResolvedValue(mockUser as any);
|
|
229
|
+
|
|
230
|
+
// When
|
|
231
|
+
const result = await userRepository.findById("user-456");
|
|
232
|
+
|
|
233
|
+
// Then
|
|
234
|
+
expect(isSuccess(result)).toBe(true);
|
|
235
|
+
if (isSuccess(result)) {
|
|
236
|
+
expect(result.value?.id).toBe("user-456");
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("見つからない場合はnullを返す", async () => {
|
|
241
|
+
// Given
|
|
242
|
+
vi.mocked(prisma.user.findUnique).mockResolvedValue(null);
|
|
243
|
+
|
|
244
|
+
// When
|
|
245
|
+
const result = await userRepository.findById("nonexistent");
|
|
246
|
+
|
|
247
|
+
// Then
|
|
248
|
+
expect(isSuccess(result)).toBe(true);
|
|
249
|
+
if (isSuccess(result)) {
|
|
250
|
+
expect(result.value).toBeNull();
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe("findByEmail", () => {
|
|
256
|
+
it("メールアドレスでユーザーを取得できる", async () => {
|
|
257
|
+
// Given
|
|
258
|
+
const mockUser = await UserFactory.build({ email: "specific@example.com" });
|
|
259
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
260
|
+
vi.mocked(prisma.user.findUnique).mockResolvedValue(mockUser as any);
|
|
261
|
+
|
|
262
|
+
// When
|
|
263
|
+
const result = await userRepository.findByEmail("specific@example.com");
|
|
264
|
+
|
|
265
|
+
// Then
|
|
266
|
+
expect(isSuccess(result)).toBe(true);
|
|
267
|
+
if (isSuccess(result)) {
|
|
268
|
+
expect(result.value?.email).toBe("specific@example.com");
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("見つからない場合はnullを返す", async () => {
|
|
273
|
+
// Given
|
|
274
|
+
vi.mocked(prisma.user.findUnique).mockResolvedValue(null);
|
|
275
|
+
|
|
276
|
+
// When
|
|
277
|
+
const result = await userRepository.findByEmail("notfound@example.com");
|
|
278
|
+
|
|
279
|
+
// Then
|
|
280
|
+
expect(isSuccess(result)).toBe(true);
|
|
281
|
+
if (isSuccess(result)) {
|
|
282
|
+
expect(result.value).toBeNull();
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe("updateLastLogin", () => {
|
|
288
|
+
it("最終ログイン日時を更新できる", async () => {
|
|
289
|
+
// Given
|
|
290
|
+
const loginTime = new Date("2025-01-03T12:00:00Z");
|
|
291
|
+
const updatedUser = await UserFactory.build({ lastLogin: loginTime });
|
|
292
|
+
// biome-ignore lint/suspicious/noExplicitAny: test with factory-generated data
|
|
293
|
+
vi.mocked(prisma.user.update).mockResolvedValue(updatedUser as any);
|
|
294
|
+
|
|
295
|
+
// When
|
|
296
|
+
const result = await userRepository.updateLastLogin("user-123", loginTime);
|
|
297
|
+
|
|
298
|
+
// Then
|
|
299
|
+
expect(isSuccess(result)).toBe(true);
|
|
300
|
+
expect(prisma.user.update).toHaveBeenCalledWith({
|
|
301
|
+
where: { id: "user-123" },
|
|
302
|
+
data: { lastLogin: loginTime },
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("存在しないユーザーの更新でfailure Resultを返す", async () => {
|
|
307
|
+
// Given
|
|
308
|
+
vi.mocked(prisma.user.update).mockRejectedValue(new Error("Record not found"));
|
|
309
|
+
|
|
310
|
+
// When
|
|
311
|
+
const result = await userRepository.updateLastLogin("nonexistent", new Date());
|
|
312
|
+
|
|
313
|
+
// Then
|
|
314
|
+
expect(isFailure(result)).toBe(true);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
});
|