sonamu 0.10.7 → 0.10.9

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 (79) hide show
  1. package/dist/api/decorators.d.ts.map +1 -1
  2. package/dist/api/decorators.js +5 -12
  3. package/dist/bin/cli.js +10 -253
  4. package/dist/database/base-model.d.ts +1 -0
  5. package/dist/database/base-model.d.ts.map +1 -1
  6. package/dist/database/base-model.js +14 -8
  7. package/dist/database/db.d.ts +2 -1
  8. package/dist/database/db.d.ts.map +1 -1
  9. package/dist/database/db.js +13 -3
  10. package/dist/database/puri-wrapper.d.ts.map +1 -1
  11. package/dist/database/puri-wrapper.js +4 -11
  12. package/dist/database/transaction-context.d.ts +8 -3
  13. package/dist/database/transaction-context.d.ts.map +1 -1
  14. package/dist/database/transaction-context.js +12 -7
  15. package/dist/migration/code-generation.js +2 -2
  16. package/dist/naite/naite-reporter.d.ts +8 -3
  17. package/dist/naite/naite-reporter.d.ts.map +1 -1
  18. package/dist/naite/naite-reporter.js +9 -4
  19. package/dist/ui-web/assets/{index-D0MHYbxl.js → index-CJf8uJYf.js} +47 -41
  20. package/dist/ui-web/assets/index-GMMIVGja.css +1 -0
  21. package/dist/ui-web/index.html +2 -2
  22. package/package.json +3 -3
  23. package/src/api/decorators.ts +11 -22
  24. package/src/bin/cli.ts +12 -375
  25. package/src/database/__tests__/transaction-scope.test.ts +227 -0
  26. package/src/database/base-model.ts +31 -6
  27. package/src/database/db.ts +17 -2
  28. package/src/database/puri-wrapper.ts +5 -22
  29. package/src/database/transaction-context.ts +14 -6
  30. package/src/migration/code-generation.ts +1 -1
  31. package/src/naite/naite-reporter.ts +8 -3
  32. package/dist/ui-web/assets/index-Dx_JX4aQ.css +0 -1
  33. package/src/skills/AGENTS.md +0 -108
  34. package/src/skills/sonamu/SKILL.md +0 -75
  35. package/src/skills/sonamu-ai-agents/SKILL.md +0 -205
  36. package/src/skills/sonamu-api/SKILL.md +0 -480
  37. package/src/skills/sonamu-auth/SKILL.md +0 -327
  38. package/src/skills/sonamu-auth/references/plugins.md +0 -310
  39. package/src/skills/sonamu-auth/references/user-id-migration-followups.md +0 -192
  40. package/src/skills/sonamu-auth/references/user-id-migration.md +0 -455
  41. package/src/skills/sonamu-config/SKILL.md +0 -203
  42. package/src/skills/sonamu-config/references/database.md +0 -452
  43. package/src/skills/sonamu-config/references/environments.md +0 -178
  44. package/src/skills/sonamu-config/references/server-options.md +0 -400
  45. package/src/skills/sonamu-entity/SKILL.md +0 -180
  46. package/src/skills/sonamu-entity/references/creation-workflow.md +0 -581
  47. package/src/skills/sonamu-entity/references/design-guides.md +0 -243
  48. package/src/skills/sonamu-entity/references/field-types.md +0 -170
  49. package/src/skills/sonamu-entity/references/relations-detail.md +0 -245
  50. package/src/skills/sonamu-entity/references/relations.md +0 -463
  51. package/src/skills/sonamu-entity/references/subset.md +0 -156
  52. package/src/skills/sonamu-fixture/SKILL.md +0 -180
  53. package/src/skills/sonamu-fixture/references/cli-usage.md +0 -439
  54. package/src/skills/sonamu-fixture/references/cone.md +0 -298
  55. package/src/skills/sonamu-frontend/SKILL.md +0 -142
  56. package/src/skills/sonamu-frontend/references/components.md +0 -323
  57. package/src/skills/sonamu-frontend/references/examples.md +0 -64
  58. package/src/skills/sonamu-frontend/references/hooks.md +0 -273
  59. package/src/skills/sonamu-frontend/references/runtime.md +0 -165
  60. package/src/skills/sonamu-frontend/references/scaffolding.md +0 -439
  61. package/src/skills/sonamu-i18n/SKILL.md +0 -287
  62. package/src/skills/sonamu-migration/SKILL.md +0 -316
  63. package/src/skills/sonamu-naite/SKILL.md +0 -266
  64. package/src/skills/sonamu-query/SKILL.md +0 -48
  65. package/src/skills/sonamu-query/references/model-patterns.md +0 -390
  66. package/src/skills/sonamu-query/references/model.md +0 -366
  67. package/src/skills/sonamu-query/references/puri.md +0 -413
  68. package/src/skills/sonamu-query/references/search.md +0 -238
  69. package/src/skills/sonamu-query/references/upsert.md +0 -324
  70. package/src/skills/sonamu-tasks/SKILL.md +0 -236
  71. package/src/skills/sonamu-testing/SKILL.md +0 -251
  72. package/src/skills/sonamu-testing/references/devrunner.md +0 -405
  73. package/src/skills/sonamu-testing/references/helpers.md +0 -185
  74. package/src/skills/sonamu-testing/references/patterns.md +0 -263
  75. package/src/skills/sonamu-testing/references/pitfalls.md +0 -588
  76. package/src/skills/sonamu-testing/references/quick-start.md +0 -285
  77. package/src/skills/sonamu-testing/references/type-safety.md +0 -172
  78. package/src/skills/sonamu-testing/references/writing-plan.md +0 -375
  79. package/src/skills/sonamu-vector/SKILL.md +0 -222
@@ -1,588 +0,0 @@
1
- # Pitfalls and Complex Scenarios
2
-
3
- ## Practical Notes (Common Pitfalls)
4
-
5
- ### 1. Fixture Data Preparation Required
6
-
7
- **Problem:** Tests fail without base data due to foreign key constraints
8
-
9
- **Solution:**
10
-
11
- ```sql
12
- -- database/scripts/seed-initial-data.sql
13
- INSERT INTO institutions (id, name, code) VALUES (1, 'HQ', 'HQ');
14
- INSERT INTO departments (id, name, institution_id) VALUES (1, 'Research', 1);
15
- INSERT INTO roles (id, code, name) VALUES (1, 'ADMIN', 'Administrator');
16
- ```
17
-
18
- ```bash
19
- # 1. apply seed data to test DB
20
- PGPASSWORD=1234 psql -h 0.0.0.0 -U postgres -d project_test -f database/scripts/seed-initial-data.sql
21
-
22
- # 2. create dump
23
- pnpm dump
24
-
25
- # 3. apply to fixture DB
26
- pnpm seed
27
-
28
- # 4. sonamu fixture sync (optional)
29
- pnpm sonamu fixture sync
30
- ```
31
-
32
- ### 2. SaveParams Type Design (Partial)
33
-
34
- **Problem 1:** Type error occurs when changing only some fields on update
35
-
36
- **Problem 2:** Type error occurs when receiving overrides as Partial in test helpers
37
-
38
- ```typescript
39
- // WRONG - nullable fields not set to partial
40
- export const QuestionSaveParams = QuestionBaseSchema.partial({
41
- id: true,
42
- created_at: true,
43
- });
44
-
45
- // test-helpers.ts
46
- export async function createTestQuestion(
47
- collectionId: number,
48
- override?: Partial<QuestionSaveParams>,
49
- ) {
50
- const [id] = await QuestionModel.save([
51
- {
52
- content: "test question",
53
- parent_id: null,
54
- answer_group_id: null,
55
- ...override, // type error: undefined cannot be assigned to null
56
- },
57
- ]);
58
- return id;
59
- }
60
- ```
61
-
62
- **Solution:** Set nullable/dbDefault fields to partial
63
-
64
- ```typescript
65
- // api/src/application/user/user.types.ts
66
- export const UserSaveParams = UserBaseSchema.partial({
67
- id: true, // needed for update
68
- created_at: true, // dbDefault
69
- password: true, // nullable
70
- email: true, // nullable
71
- phone: true, // nullable
72
- user_type: true, // dbDefault
73
- position_code: true, // nullable
74
- position_name: true, // nullable
75
- hire_date: true, // nullable
76
- status: true, // dbDefault
77
- department_id: true, // nullable relation
78
- });
79
- ```
80
-
81
- **Application criteria:**
82
-
83
- - id, created_at, updated_at: always partial (auto-generated)
84
- - Fields with dbDefault: set to partial
85
- - FK fields with nullable: true: set to partial
86
- - Regular fields with nullable: true (e.g. description): set to partial
87
-
88
- **Key:** Required fields (employee_no, login_id, name, institution_id) are excluded from partial to maintain type safety
89
-
90
- ### 3. Excluding Relation Fields on Update
91
-
92
- **Problem:** Subset includes relation objects, but SaveParams only has FK, causing errors
93
-
94
- ```typescript
95
- // WRONG
96
- const user = await UserModel.findById("A", userId);
97
- await UserModel.save([{ ...user, status: "inactive" }]);
98
- // → "column 'department' does not exist" error
99
- ```
100
-
101
- **Solution:** Exclude relation fields + explicitly add FK
102
-
103
- ```typescript
104
- // CORRECT
105
- const user = await UserModel.findById("A", userId);
106
- const { institution, department, ...userData } = user;
107
- await UserModel.save([
108
- {
109
- ...userData,
110
- institution_id: user.institution.id, // explicitly add FK
111
- department_id: user.department?.id ?? null,
112
- status: "inactive",
113
- },
114
- ]);
115
- ```
116
-
117
- **Reason:** `UserSubsetA` includes `institution`, `department` objects, but does not include `institution_id`, `department_id` FKs
118
-
119
- ### 4. ubUpsert is an Upsert Operation
120
-
121
- **Problem:** Unique constraint violation tests fail
122
-
123
- ```typescript
124
- // failing test
125
- test("employee number must be unique", async () => {
126
- await UserModel.save([{ employee_no: "001", ... }]);
127
-
128
- // attempt to create with duplicate employee number
129
- await expect(
130
- UserModel.save([{ employee_no: "001", ... }])
131
- ).rejects.toThrow(); // does not throw error, performs UPDATE instead
132
- });
133
- ```
134
-
135
- **Cause:** Sonamu's `save()` uses `ubUpsert` → on conflict, performs UPDATE instead of throwing error
136
-
137
- **Solution:** Skip such tests
138
-
139
- ```typescript
140
- test.skip("employee number must be unique (skipped because ubUpsert performs upsert)", async () => {
141
- // ...
142
- });
143
- ```
144
-
145
- ### 5. testAs Usage
146
-
147
- **Problem:** Calling testAs inside test causes an error
148
-
149
- ```typescript
150
- // WRONG
151
- test("permission test", async () => {
152
- await testAs(adminUser, "description", async () => { ... });
153
- // → "Calling the test function inside another test function is not allowed" error
154
- });
155
-
156
- // CORRECT - use as a replacement for test
157
- testAs(adminUser, "permission test", async () => {
158
- const result = await UserModel.del([userId]);
159
- expect(result).toBe(1);
160
- });
161
- ```
162
-
163
- ### 6. Validating Model Queries with Naite
164
-
165
- **Add Naite recording to Model:**
166
-
167
- ```typescript
168
- // user.model.ts
169
- import { Naite } from "sonamu";
170
-
171
- async findMany(...) {
172
- // ... build qb ...
173
-
174
- // record query for testing
175
- Naite.t("esq-query", qb.toQuery());
176
-
177
- return this.executeSubsetQuery({ ... });
178
- }
179
- ```
180
-
181
- **Validate in test:**
182
-
183
- ```typescript
184
- test("should not have limit when num: 0", async () => {
185
- await UserModel.findMany("A", { num: 0, page: 1 });
186
-
187
- expect(Naite.get("esq-query").first()).not.contain("limit");
188
- expect(Naite.get("esq-query").first()).not.contain("offset");
189
- });
190
- ```
191
-
192
- ### 7. Consider Multilingual Error Messages
193
-
194
- ```typescript
195
- // WRONG: only validates English message
196
- await expect(UserModel.findById("A", 99999)).rejects.toThrow("not found");
197
-
198
- // CORRECT: partial match on actual error message
199
- await expect(UserModel.findById("A", 99999)).rejects.toThrow("does not exist");
200
- ```
201
-
202
- ### 8. pnpm Workspace and Vitest Instance Conflicts
203
-
204
- **Problem:** "Vitest failed to access its internal state" error
205
-
206
- **Cause:** When sonamu is connected via `link:`, sonamu and the project's vitest are installed at separate paths with different peer dependency combinations
207
-
208
- **Temporary fix (for testing):**
209
-
210
- ```json
211
- // packages/api/package.json
212
- {
213
- "dependencies": {
214
- "sonamu": "0.8.0" // specify version instead of link
215
- }
216
- }
217
- ```
218
-
219
- **Fundamental fix:** Contact sonamu developers (framework internal issue)
220
-
221
- ### 9. assert() for Truthy Checks
222
-
223
- ```typescript
224
- import assert from "assert";
225
-
226
- test("create user", async () => {
227
- const [userId] = await UserModel.save([{ ... }]);
228
-
229
- // truthy check
230
- assert(userId);
231
-
232
- // userId is now safely inferred as number
233
- const user = await UserModel.findById("A", userId);
234
- });
235
- ```
236
-
237
- ### 10. Create Test Data Directly
238
-
239
- **miomock convention:** Minimize fixtures, create data directly within tests
240
-
241
- ```typescript
242
- // recommended pattern
243
- test("create user", async () => {
244
- const [userId] = await UserModel.save([
245
- {
246
- employee_no: "2026001",
247
- login_id: "testuser",
248
- name: "Test User",
249
- institution_id: 1,
250
- // ... required fields
251
- },
252
- ]);
253
-
254
- const user = await UserModel.findById("A", userId);
255
- expect(user.name).toBe("Test User");
256
- });
257
-
258
- // Fixtures only for shared data
259
- const f = await loadFixtures(["institution01"]); // only for shared data like institutions
260
- ```
261
-
262
- ## Complex Entity Test Strategy
263
-
264
- When dependencies between entities are complex (Institution → Department → User → Task → TaskParticipant), use test helper functions.
265
-
266
- ### Defining Test Helper Functions
267
-
268
- ```typescript
269
- // api/src/testing/test-helpers.ts
270
- import assert from "assert";
271
- import { InstitutionModel } from "../application/institution/institution.model";
272
- import { DepartmentModel } from "../application/department/department.model";
273
- import { UserModel } from "../application/user/user.model";
274
- import { TaskModel } from "../application/task/task.model";
275
-
276
- // each helper requires only the minimum required fields and provides defaults for the rest
277
- let counter = 0;
278
- function uniqueId(prefix: string) {
279
- return `${prefix}_${Date.now()}_${++counter}`;
280
- }
281
-
282
- export async function createTestInstitution(override?: Partial<InstitutionSaveParams>) {
283
- const [id] = await InstitutionModel.save([
284
- {
285
- name: "Test Institution",
286
- code: uniqueId("INST"),
287
- ...override,
288
- },
289
- ]);
290
- assert(id);
291
- return id;
292
- }
293
-
294
- export async function createTestDepartment(
295
- institutionId: number,
296
- override?: Partial<DepartmentSaveParams>,
297
- ) {
298
- const [id] = await DepartmentModel.save([
299
- {
300
- name: "Test Department",
301
- code: uniqueId("DEPT"),
302
- dept_type: "division",
303
- institution_id: institutionId,
304
- is_active: true,
305
- sort_order: 0,
306
- ...override,
307
- },
308
- ]);
309
- assert(id);
310
- return id;
311
- }
312
-
313
- export async function createTestUser(institutionId: number, override?: Partial<UserSaveParams>) {
314
- const [id] = await UserModel.save([
315
- {
316
- employee_no: uniqueId("EMP"),
317
- login_id: uniqueId("login"),
318
- name: "Test User",
319
- institution_id: institutionId,
320
- ...override,
321
- },
322
- ]);
323
- assert(id);
324
- return id;
325
- }
326
-
327
- export async function createTestTask(
328
- principalInvestigatorId: number,
329
- override?: Partial<TaskSaveParams>,
330
- ) {
331
- const [id] = await TaskModel.save([
332
- {
333
- task_no: uniqueId("TASK"),
334
- title: "Test Task",
335
- year: new Date().getFullYear(),
336
- begin_date: new Date(),
337
- end_date: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000),
338
- principal_investigator_id: principalInvestigatorId,
339
- ...override,
340
- },
341
- ]);
342
- assert(id);
343
- return id;
344
- }
345
-
346
- // create the entire dependency chain at once
347
- export async function createTestTaskWithDeps(taskOverride?: Partial<TaskSaveParams>) {
348
- const institutionId = await createTestInstitution();
349
- const userId = await createTestUser(institutionId);
350
- const taskId = await createTestTask(userId, taskOverride);
351
- return { institutionId, userId, taskId };
352
- }
353
-
354
- export async function createTestUserWithDeps(userOverride?: Partial<UserSaveParams>) {
355
- const institutionId = await createTestInstitution();
356
- const userId = await createTestUser(institutionId, userOverride);
357
- return { institutionId, userId };
358
- }
359
- ```
360
-
361
- ### Using in Tests
362
-
363
- ```typescript
364
- import { createTestTaskWithDeps, createTestUser } from "../../testing/test-helpers";
365
-
366
- describe("TaskModel", () => {
367
- // GOOD: concise with helper functions
368
- test("Create - create with minimum required fields", async () => {
369
- const { taskId } = await createTestTaskWithDeps();
370
-
371
- const task = await TaskModel.findById("D", taskId);
372
- expect(task.id).toBe(taskId);
373
- });
374
-
375
- // GOOD: customize specific fields
376
- test("Create - create with specific status", async () => {
377
- const { taskId } = await createTestTaskWithDeps({
378
- status: "approved",
379
- title: "Approved Task",
380
- });
381
-
382
- const task = await TaskModel.findById("D", taskId);
383
- expect(task.status).toBe("approved");
384
- });
385
-
386
- // BAD: creating dependencies directly in every test (repetitive)
387
- test("Create - direct creation (not recommended)", async () => {
388
- const [institutionId] = await InstitutionModel.save([{ name: "...", code: "..." }]);
389
- assert(institutionId);
390
- const [userId] = await UserModel.save([{ ... }]);
391
- assert(userId);
392
- const [taskId] = await TaskModel.save([{ ... }]);
393
- assert(taskId);
394
- // ...
395
- });
396
- });
397
- ```
398
-
399
- ### Subset → SaveParams Conversion Helper
400
-
401
- When modifying findById results and saving again, relations must be converted to FKs:
402
-
403
- ```typescript
404
- // api/src/testing/test-helpers.ts
405
-
406
- // Task Subset A → SaveParams conversion
407
- export function taskToSaveParams(task: TaskSubsetA): TaskSaveParams {
408
- const { program, project, principal_investigator, department, prev_task, ...rest } = task;
409
-
410
- return {
411
- ...rest,
412
- program_id: program?.id ?? null,
413
- project_id: project?.id ?? null,
414
- principal_investigator_id: principal_investigator.id,
415
- department_id: department?.id ?? null,
416
- prev_task_id: prev_task?.id ?? null,
417
- };
418
- }
419
-
420
- // generic helper (note: write directly if relation field names differ)
421
- export function relationToFk<T extends Record<string, any>>(
422
- data: T,
423
- relationFields: string[],
424
- ): Record<string, any> {
425
- const result: Record<string, any> = {};
426
-
427
- for (const [key, value] of Object.entries(data)) {
428
- if (relationFields.includes(key)) {
429
- // relation → FK
430
- result[`${key}_id`] = value?.id ?? null;
431
- } else {
432
- result[key] = value;
433
- }
434
- }
435
-
436
- return result;
437
- }
438
- ```
439
-
440
- ### Simplifying Update Tests
441
-
442
- ```typescript
443
- import { createTestTaskWithDeps, taskToSaveParams } from "../../testing/test-helpers";
444
-
445
- test("Update - update task info", async () => {
446
- const { taskId } = await createTestTaskWithDeps();
447
-
448
- const task = await TaskModel.findById("A", taskId);
449
- await TaskModel.save([
450
- {
451
- ...taskToSaveParams(task),
452
- title: "Updated Title",
453
- },
454
- ]);
455
-
456
- const updated = await TaskModel.findById("A", taskId);
457
- expect(updated.title).toBe("Updated Title");
458
- });
459
- ```
460
-
461
- ### Notes
462
-
463
- **Do not use beforeAll/beforeEach:**
464
-
465
- In sonamu's test environment, creating data with beforeAll/beforeEach may end up referencing sonamu internal code. Instead, call helper functions within each test.
466
-
467
- ```typescript
468
- // WRONG: using beforeAll
469
- describe("TaskModel", () => {
470
- let taskId: number;
471
- beforeAll(async () => {
472
- const result = await createTestTaskWithDeps();
473
- taskId = result.taskId;
474
- });
475
-
476
- test("...", async () => {
477
- // using taskId - may cause problems
478
- });
479
- });
480
-
481
- // CORRECT: create in each test
482
- describe("TaskModel", () => {
483
- test("...", async () => {
484
- const { taskId } = await createTestTaskWithDeps();
485
- // use taskId
486
- });
487
- });
488
- ```
489
-
490
- ---
491
-
492
- ## Common Mistakes and Solutions
493
-
494
- ### ubUpsert Does Not Throw Unique Constraint Errors
495
-
496
- **→ See "Practical Notes #4. ubUpsert is an Upsert Operation" above**
497
-
498
- ### Transaction Isolation and Test Isolation
499
-
500
- Each test runs in an independent transaction so data is isolated. Even within the same test, data you created may not be immediately visible in queries.
501
-
502
- ```typescript
503
- // BAD: expecting exact count may fail
504
- test("search by role name", async () => {
505
- await createTestRole({ name: "AdminA" });
506
- await createTestRole({ name: "AdminB" });
507
-
508
- const { rows } = await RoleModel.findMany("A", {
509
- keyword: "Admin",
510
- });
511
-
512
- // may not see 2 due to transaction isolation
513
- expect(rows.length).toBe(2);
514
- });
515
-
516
- // GOOD: use unique identifier and flexible assertion
517
- test("search by role name", async () => {
518
- // unique identifier to prevent conflicts
519
- const testName = `SearchTest_${Date.now()}`;
520
- await createTestRole({ name: `${testName}A` });
521
- await createTestRole({ name: `${testName}B` });
522
-
523
- const { rows } = await RoleModel.findMany("A", {
524
- keyword: testName,
525
- });
526
-
527
- // verify at least 1
528
- expect(rows.length).toBeGreaterThanOrEqual(1);
529
- // content validation
530
- expect(rows.some((r) => r.name.includes(testName))).toBe(true);
531
- });
532
- ```
533
-
534
- **Patterns:**
535
-
536
- - Use unique identifiers: `Date.now()`, `uuid()`, etc. to prevent conflicts
537
- - Flexible assertions: use `toBeGreaterThanOrEqual(1)` instead of `toBe(2)`
538
- - Content validation: verify actual data matches rather than count
539
-
540
- ### Conditional Validation for Sorting Tests
541
-
542
- Since not all data may be returned in sorting tests, use conditional validation:
543
-
544
- ```typescript
545
- // BAD: assumes two items are always returned
546
- test("sort - newest ID first", async () => {
547
- const id1 = await createTestRole({ name: "Role1" });
548
- const id2 = await createTestRole({ name: "Role2" });
549
-
550
- const { rows } = await RoleModel.findMany("A", {
551
- orderBy: "id-desc",
552
- });
553
-
554
- const id2Index = rows.findIndex((r) => r.id === id2);
555
- const id1Index = rows.findIndex((r) => r.id === id1);
556
-
557
- // fails if either is missing
558
- expect(id2Index).toBeLessThan(id1Index);
559
- });
560
-
561
- // GOOD: conditional validation
562
- test("sort - newest ID first", async () => {
563
- const id1 = await createTestRole({ name: "Role1" });
564
- const id2 = await createTestRole({ name: "Role2" });
565
-
566
- const { rows } = await RoleModel.findMany("A", {
567
- orderBy: "id-desc",
568
- });
569
-
570
- const testRoles = rows.filter((r) => [id1, id2].includes(r.id));
571
- expect(testRoles.length).toBeGreaterThanOrEqual(1);
572
-
573
- // only validate order when both roles are returned
574
- if (testRoles.length === 2) {
575
- const id2Index = rows.findIndex((r) => r.id === id2);
576
- const id1Index = rows.findIndex((r) => r.id === id1);
577
- expect(id2Index).toBeLessThan(id1Index);
578
- }
579
- });
580
- ```
581
-
582
- **Key:** Accept the uncertainty caused by transaction isolation, and only assert when validation is possible.
583
-
584
- ---
585
-
586
- ## Fixture Data Creation Tips
587
-
588
- **→ Detailed guide (unique constraint handling, gen vs fetch selection, DB sequence reset, FixtureGenerator customization): `sonamu-fixture` "Practical Tips" section**