@malamute/ai-rules 1.0.0 → 1.2.0

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.
Files changed (133) hide show
  1. package/README.md +270 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
  4. package/configs/_shared/.claude/rules/conventions/git.md +265 -0
  5. package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
  6. package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
  7. package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
  8. package/configs/_shared/.claude/rules/devops/docker.md +275 -0
  9. package/configs/_shared/.claude/rules/devops/nx.md +194 -0
  10. package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
  11. package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
  12. package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
  13. package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
  14. package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
  15. package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
  16. package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
  17. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
  18. package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
  19. package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
  20. package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
  21. package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
  22. package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
  23. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
  24. package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
  25. package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
  26. package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
  27. package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
  28. package/configs/_shared/.claude/rules/quality/logging.md +45 -0
  29. package/configs/_shared/.claude/rules/quality/observability.md +240 -0
  30. package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
  31. package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
  32. package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
  33. package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
  34. package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
  35. package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  36. package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  37. package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  38. package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
  39. package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
  40. package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
  41. package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
  42. package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
  43. package/configs/_shared/CLAUDE.md +52 -149
  44. package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
  45. package/configs/angular/.claude/rules/core/resource.md +285 -0
  46. package/configs/angular/.claude/rules/core/signals.md +323 -0
  47. package/configs/angular/.claude/rules/http.md +338 -0
  48. package/configs/angular/.claude/rules/routing.md +291 -0
  49. package/configs/angular/.claude/rules/ssr.md +312 -0
  50. package/configs/angular/.claude/rules/state/signal-store.md +408 -0
  51. package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
  52. package/configs/angular/.claude/rules/testing.md +7 -7
  53. package/configs/angular/.claude/rules/ui/aria.md +422 -0
  54. package/configs/angular/.claude/rules/ui/forms.md +424 -0
  55. package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
  56. package/configs/angular/.claude/settings.json +1 -0
  57. package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
  58. package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
  59. package/configs/angular/CLAUDE.md +24 -216
  60. package/configs/dotnet/.claude/rules/background-services.md +552 -0
  61. package/configs/dotnet/.claude/rules/configuration.md +426 -0
  62. package/configs/dotnet/.claude/rules/ddd.md +447 -0
  63. package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/.claude/rules/mediatr.md +320 -0
  65. package/configs/dotnet/.claude/rules/middleware.md +489 -0
  66. package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/.claude/rules/validation.md +388 -0
  68. package/configs/dotnet/.claude/settings.json +21 -3
  69. package/configs/dotnet/CLAUDE.md +53 -286
  70. package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/.claude/rules/dependencies.md +170 -0
  72. package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
  73. package/configs/fastapi/.claude/rules/lifespan.md +274 -0
  74. package/configs/fastapi/.claude/rules/middleware.md +229 -0
  75. package/configs/fastapi/.claude/rules/pydantic.md +433 -0
  76. package/configs/fastapi/.claude/rules/responses.md +251 -0
  77. package/configs/fastapi/.claude/rules/routers.md +202 -0
  78. package/configs/fastapi/.claude/rules/security.md +222 -0
  79. package/configs/fastapi/.claude/rules/testing.md +251 -0
  80. package/configs/fastapi/.claude/rules/websockets.md +298 -0
  81. package/configs/fastapi/.claude/settings.json +33 -0
  82. package/configs/fastapi/CLAUDE.md +144 -0
  83. package/configs/flask/.claude/rules/blueprints.md +208 -0
  84. package/configs/flask/.claude/rules/cli.md +285 -0
  85. package/configs/flask/.claude/rules/configuration.md +281 -0
  86. package/configs/flask/.claude/rules/context.md +238 -0
  87. package/configs/flask/.claude/rules/error-handlers.md +278 -0
  88. package/configs/flask/.claude/rules/extensions.md +278 -0
  89. package/configs/flask/.claude/rules/flask.md +171 -0
  90. package/configs/flask/.claude/rules/marshmallow.md +206 -0
  91. package/configs/flask/.claude/rules/security.md +267 -0
  92. package/configs/flask/.claude/rules/testing.md +284 -0
  93. package/configs/flask/.claude/settings.json +33 -0
  94. package/configs/flask/CLAUDE.md +166 -0
  95. package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/.claude/rules/filters.md +376 -0
  97. package/configs/nestjs/.claude/rules/interceptors.md +317 -0
  98. package/configs/nestjs/.claude/rules/middleware.md +321 -0
  99. package/configs/nestjs/.claude/rules/modules.md +26 -0
  100. package/configs/nestjs/.claude/rules/pipes.md +351 -0
  101. package/configs/nestjs/.claude/rules/websockets.md +451 -0
  102. package/configs/nestjs/.claude/settings.json +16 -2
  103. package/configs/nestjs/CLAUDE.md +57 -215
  104. package/configs/nextjs/.claude/rules/api-routes.md +358 -0
  105. package/configs/nextjs/.claude/rules/authentication.md +355 -0
  106. package/configs/nextjs/.claude/rules/components.md +52 -0
  107. package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/.claude/rules/database.md +400 -0
  109. package/configs/nextjs/.claude/rules/middleware.md +303 -0
  110. package/configs/nextjs/.claude/rules/routing.md +324 -0
  111. package/configs/nextjs/.claude/rules/seo.md +350 -0
  112. package/configs/nextjs/.claude/rules/server-actions.md +353 -0
  113. package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
  114. package/configs/nextjs/.claude/settings.json +5 -0
  115. package/configs/nextjs/CLAUDE.md +69 -331
  116. package/package.json +23 -9
  117. package/src/cli.js +220 -0
  118. package/src/config.js +29 -0
  119. package/src/index.js +13 -0
  120. package/src/installer.js +361 -0
  121. package/src/merge.js +116 -0
  122. package/src/tech-config.json +29 -0
  123. package/src/utils.js +96 -0
  124. package/configs/python/.claude/rules/flask.md +0 -332
  125. package/configs/python/.claude/settings.json +0 -18
  126. package/configs/python/CLAUDE.md +0 -273
  127. package/src/install.js +0 -315
  128. /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
  129. /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
  130. /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
  131. /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
  132. /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
  133. /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
@@ -0,0 +1,383 @@
1
+ ---
2
+ paths:
3
+ - "**/repositories/**/*.py"
4
+ - "**/repository/**/*.py"
5
+ - "**/*_repository.py"
6
+ - "**/dal/**/*.py"
7
+ ---
8
+
9
+ # Python Repository Pattern
10
+
11
+ ## Base Repository (SQLAlchemy)
12
+
13
+ ```python
14
+ # repositories/base.py
15
+ from typing import Generic, TypeVar, Sequence
16
+ from sqlalchemy import select, func
17
+ from sqlalchemy.ext.asyncio import AsyncSession
18
+
19
+ from models.base import Base
20
+
21
+ ModelT = TypeVar("ModelT", bound=Base)
22
+
23
+
24
+ class BaseRepository(Generic[ModelT]):
25
+ """Base repository with common CRUD operations."""
26
+
27
+ def __init__(self, session: AsyncSession, model: type[ModelT]):
28
+ self.session = session
29
+ self.model = model
30
+
31
+ async def get(self, id: int) -> ModelT | None:
32
+ return await self.session.get(self.model, id)
33
+
34
+ async def get_or_raise(self, id: int) -> ModelT:
35
+ entity = await self.get(id)
36
+ if not entity:
37
+ raise NotFoundError(self.model.__name__, id)
38
+ return entity
39
+
40
+ async def get_all(
41
+ self,
42
+ *,
43
+ skip: int = 0,
44
+ limit: int = 100,
45
+ ) -> Sequence[ModelT]:
46
+ result = await self.session.scalars(
47
+ select(self.model).offset(skip).limit(limit)
48
+ )
49
+ return result.all()
50
+
51
+ async def count(self) -> int:
52
+ result = await self.session.scalar(
53
+ select(func.count()).select_from(self.model)
54
+ )
55
+ return result or 0
56
+
57
+ async def create(self, **kwargs) -> ModelT:
58
+ entity = self.model(**kwargs)
59
+ self.session.add(entity)
60
+ await self.session.flush()
61
+ await self.session.refresh(entity)
62
+ return entity
63
+
64
+ async def update(self, entity: ModelT, **kwargs) -> ModelT:
65
+ for key, value in kwargs.items():
66
+ if hasattr(entity, key):
67
+ setattr(entity, key, value)
68
+ await self.session.flush()
69
+ await self.session.refresh(entity)
70
+ return entity
71
+
72
+ async def delete(self, entity: ModelT) -> None:
73
+ await self.session.delete(entity)
74
+ await self.session.flush()
75
+
76
+ async def exists(self, id: int) -> bool:
77
+ result = await self.session.scalar(
78
+ select(func.count())
79
+ .select_from(self.model)
80
+ .where(self.model.id == id)
81
+ )
82
+ return (result or 0) > 0
83
+ ```
84
+
85
+ ## Typed Repository
86
+
87
+ ```python
88
+ # repositories/user.py
89
+ from sqlalchemy import select
90
+ from sqlalchemy.ext.asyncio import AsyncSession
91
+ from sqlalchemy.orm import selectinload
92
+
93
+ from models import User
94
+ from schemas import UserCreate, UserUpdate
95
+ from .base import BaseRepository
96
+
97
+
98
+ class UserRepository(BaseRepository[User]):
99
+ def __init__(self, session: AsyncSession):
100
+ super().__init__(session, User)
101
+
102
+ async def get_by_email(self, email: str) -> User | None:
103
+ result = await self.session.scalar(
104
+ select(User).where(User.email == email)
105
+ )
106
+ return result
107
+
108
+ async def get_with_posts(self, user_id: int) -> User | None:
109
+ result = await self.session.scalar(
110
+ select(User)
111
+ .options(selectinload(User.posts))
112
+ .where(User.id == user_id)
113
+ )
114
+ return result
115
+
116
+ async def get_active_users(
117
+ self,
118
+ *,
119
+ skip: int = 0,
120
+ limit: int = 100,
121
+ ) -> list[User]:
122
+ result = await self.session.scalars(
123
+ select(User)
124
+ .where(User.is_active == True)
125
+ .order_by(User.created_at.desc())
126
+ .offset(skip)
127
+ .limit(limit)
128
+ )
129
+ return list(result.all())
130
+
131
+ async def search(
132
+ self,
133
+ query: str,
134
+ *,
135
+ skip: int = 0,
136
+ limit: int = 100,
137
+ ) -> list[User]:
138
+ search_term = f"%{query}%"
139
+ result = await self.session.scalars(
140
+ select(User)
141
+ .where(
142
+ (User.name.ilike(search_term)) |
143
+ (User.email.ilike(search_term))
144
+ )
145
+ .offset(skip)
146
+ .limit(limit)
147
+ )
148
+ return list(result.all())
149
+
150
+ async def create_user(self, data: UserCreate) -> User:
151
+ return await self.create(
152
+ email=data.email,
153
+ name=data.name,
154
+ hashed_password=hash_password(data.password),
155
+ )
156
+
157
+ async def update_user(self, user: User, data: UserUpdate) -> User:
158
+ update_data = data.model_dump(exclude_unset=True)
159
+ return await self.update(user, **update_data)
160
+ ```
161
+
162
+ ## Unit of Work Pattern
163
+
164
+ ```python
165
+ # repositories/uow.py
166
+ from sqlalchemy.ext.asyncio import AsyncSession
167
+
168
+ from .user import UserRepository
169
+ from .post import PostRepository
170
+ from .order import OrderRepository
171
+
172
+
173
+ class UnitOfWork:
174
+ """Unit of Work pattern for managing transactions."""
175
+
176
+ def __init__(self, session: AsyncSession):
177
+ self.session = session
178
+ self._users: UserRepository | None = None
179
+ self._posts: PostRepository | None = None
180
+ self._orders: OrderRepository | None = None
181
+
182
+ @property
183
+ def users(self) -> UserRepository:
184
+ if self._users is None:
185
+ self._users = UserRepository(self.session)
186
+ return self._users
187
+
188
+ @property
189
+ def posts(self) -> PostRepository:
190
+ if self._posts is None:
191
+ self._posts = PostRepository(self.session)
192
+ return self._posts
193
+
194
+ @property
195
+ def orders(self) -> OrderRepository:
196
+ if self._orders is None:
197
+ self._orders = OrderRepository(self.session)
198
+ return self._orders
199
+
200
+ async def commit(self) -> None:
201
+ await self.session.commit()
202
+
203
+ async def rollback(self) -> None:
204
+ await self.session.rollback()
205
+
206
+ async def __aenter__(self) -> "UnitOfWork":
207
+ return self
208
+
209
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
210
+ if exc_type:
211
+ await self.rollback()
212
+ else:
213
+ await self.commit()
214
+ await self.session.close()
215
+ ```
216
+
217
+ ## FastAPI Integration
218
+
219
+ ```python
220
+ # dependencies.py
221
+ from typing import Annotated, AsyncIterator
222
+ from fastapi import Depends
223
+ from sqlalchemy.ext.asyncio import AsyncSession
224
+
225
+ from database import async_session
226
+ from repositories import UnitOfWork, UserRepository
227
+
228
+
229
+ async def get_session() -> AsyncIterator[AsyncSession]:
230
+ async with async_session() as session:
231
+ yield session
232
+
233
+
234
+ async def get_uow(
235
+ session: Annotated[AsyncSession, Depends(get_session)]
236
+ ) -> AsyncIterator[UnitOfWork]:
237
+ async with UnitOfWork(session) as uow:
238
+ yield uow
239
+
240
+
241
+ async def get_user_repository(
242
+ session: Annotated[AsyncSession, Depends(get_session)]
243
+ ) -> UserRepository:
244
+ return UserRepository(session)
245
+
246
+
247
+ # Type aliases for dependency injection
248
+ SessionDep = Annotated[AsyncSession, Depends(get_session)]
249
+ UowDep = Annotated[UnitOfWork, Depends(get_uow)]
250
+ UserRepoDep = Annotated[UserRepository, Depends(get_user_repository)]
251
+
252
+
253
+ # Usage in routes
254
+ @router.get("/{user_id}")
255
+ async def get_user(user_id: int, repo: UserRepoDep) -> UserResponse:
256
+ user = await repo.get_or_raise(user_id)
257
+ return UserResponse.model_validate(user)
258
+
259
+
260
+ # Usage with UoW for transactions
261
+ @router.post("/transfer")
262
+ async def transfer_funds(data: TransferRequest, uow: UowDep) -> None:
263
+ sender = await uow.users.get_or_raise(data.sender_id)
264
+ receiver = await uow.users.get_or_raise(data.receiver_id)
265
+
266
+ sender.balance -= data.amount
267
+ receiver.balance += data.amount
268
+
269
+ await uow.commit() # Single transaction
270
+ ```
271
+
272
+ ## Abstract Repository (Interface)
273
+
274
+ ```python
275
+ # repositories/interfaces.py
276
+ from abc import ABC, abstractmethod
277
+ from typing import Generic, TypeVar, Sequence
278
+
279
+ T = TypeVar("T")
280
+
281
+
282
+ class IRepository(ABC, Generic[T]):
283
+ """Repository interface for dependency inversion."""
284
+
285
+ @abstractmethod
286
+ async def get(self, id: int) -> T | None:
287
+ ...
288
+
289
+ @abstractmethod
290
+ async def get_all(self, *, skip: int = 0, limit: int = 100) -> Sequence[T]:
291
+ ...
292
+
293
+ @abstractmethod
294
+ async def create(self, **kwargs) -> T:
295
+ ...
296
+
297
+ @abstractmethod
298
+ async def update(self, entity: T, **kwargs) -> T:
299
+ ...
300
+
301
+ @abstractmethod
302
+ async def delete(self, entity: T) -> None:
303
+ ...
304
+
305
+
306
+ class IUserRepository(IRepository["User"], ABC):
307
+ """User-specific repository interface."""
308
+
309
+ @abstractmethod
310
+ async def get_by_email(self, email: str) -> "User | None":
311
+ ...
312
+
313
+ @abstractmethod
314
+ async def get_active_users(self) -> list["User"]:
315
+ ...
316
+
317
+
318
+ # SQLAlchemy implementation
319
+ class SqlAlchemyUserRepository(IUserRepository):
320
+ def __init__(self, session: AsyncSession):
321
+ self.session = session
322
+
323
+ async def get(self, id: int) -> User | None:
324
+ return await self.session.get(User, id)
325
+
326
+ # ... implement all methods
327
+
328
+
329
+ # In-memory implementation for testing
330
+ class InMemoryUserRepository(IUserRepository):
331
+ def __init__(self):
332
+ self._users: dict[int, User] = {}
333
+ self._counter = 0
334
+
335
+ async def get(self, id: int) -> User | None:
336
+ return self._users.get(id)
337
+
338
+ async def create(self, **kwargs) -> User:
339
+ self._counter += 1
340
+ user = User(id=self._counter, **kwargs)
341
+ self._users[self._counter] = user
342
+ return user
343
+
344
+ # ... implement all methods
345
+ ```
346
+
347
+ ## Anti-Patterns
348
+
349
+ ```python
350
+ # BAD: Business logic in repository
351
+ class UserRepository:
352
+ async def create_user(self, data: UserCreate) -> User:
353
+ if await self.get_by_email(data.email):
354
+ raise ConflictError("User", "email", data.email) # Business logic!
355
+ # ...
356
+
357
+
358
+ # GOOD: Repository only handles data access
359
+ class UserRepository:
360
+ async def get_by_email(self, email: str) -> User | None:
361
+ # Just data access
362
+ ...
363
+
364
+
365
+ class UserService:
366
+ async def create_user(self, data: UserCreate) -> User:
367
+ if await self.repo.get_by_email(data.email):
368
+ raise ConflictError("User", "email", data.email) # In service
369
+ return await self.repo.create(...)
370
+
371
+
372
+ # BAD: Using session directly in routes
373
+ @router.get("/{user_id}")
374
+ async def get_user(user_id: int, session: SessionDep):
375
+ user = await session.get(User, user_id) # Couples route to ORM
376
+ return user
377
+
378
+
379
+ # GOOD: Use repository abstraction
380
+ @router.get("/{user_id}")
381
+ async def get_user(user_id: int, repo: UserRepoDep):
382
+ return await repo.get_or_raise(user_id) # Decoupled
383
+ ```
@@ -25,7 +25,7 @@ tests/
25
25
 
26
26
  ## Fixtures (conftest.py)
27
27
 
28
- ### FastAPI Fixtures
28
+ ### Fixtures
29
29
 
30
30
  ```python
31
31
  import pytest
@@ -105,45 +105,6 @@ async def authenticated_client(client, db_session):
105
105
  return client
106
106
  ```
107
107
 
108
- ### Flask Fixtures
109
-
110
- ```python
111
- import pytest
112
- from app import create_app, db as _db
113
-
114
- @pytest.fixture(scope="session")
115
- def app():
116
- """Create application for testing."""
117
- app = create_app("testing")
118
- return app
119
-
120
- @pytest.fixture
121
- def client(app):
122
- """Create test client."""
123
- return app.test_client()
124
-
125
- @pytest.fixture
126
- def db(app):
127
- """Create database for testing."""
128
- with app.app_context():
129
- _db.create_all()
130
- yield _db
131
- _db.drop_all()
132
-
133
- @pytest.fixture
134
- def session(db):
135
- """Create database session."""
136
- connection = db.engine.connect()
137
- transaction = connection.begin()
138
-
139
- session = db.session
140
- yield session
141
-
142
- session.close()
143
- transaction.rollback()
144
- connection.close()
145
- ```
146
-
147
108
  ## Unit Tests
148
109
 
149
110
  ```python
@@ -199,7 +160,7 @@ class TestUserService:
199
160
 
200
161
  ## Integration Tests (API)
201
162
 
202
- ### FastAPI Tests
163
+ ### API Tests
203
164
 
204
165
  ```python
205
166
  import pytest
@@ -270,34 +231,6 @@ class TestUsersAPI:
270
231
  assert "email" in response.json()
271
232
  ```
272
233
 
273
- ### Flask Tests
274
-
275
- ```python
276
- import pytest
277
-
278
- class TestUsersAPI:
279
- def test_create_user(self, client):
280
- response = client.post("/api/v1/users", json={
281
- "email": "new@example.com",
282
- "password": "password123",
283
- "name": "New User",
284
- })
285
-
286
- assert response.status_code == 201
287
- assert response.json["email"] == "new@example.com"
288
-
289
- def test_get_user(self, client, session):
290
- # Create user
291
- user = User(email="test@example.com", name="Test")
292
- session.add(user)
293
- session.commit()
294
-
295
- response = client.get(f"/api/v1/users/{user.id}")
296
-
297
- assert response.status_code == 200
298
- assert response.json["id"] == user.id
299
- ```
300
-
301
234
  ## Parametrized Tests
302
235
 
303
236
  ```python