> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# fixture

> 테스트 데이터 관리하기

export const FileItem = ({name, description, children}) => {
  const isLeaf = !children;
  const ext = getFileExtension(name);
  const extStyle = ext ? getExtensionStyle(ext) : null;
  return <div style={{
    marginLeft: "30px"
  }}>
      <div style={{
    display: "flex",
    alignItems: "center",
    gap: "4px"
  }}>
        <span style={{
    position: "relative",
    display: "inline-block"
  }}>
          <span className="file-icon">{isLeaf && !name.endsWith("/") ? "📄" : "📁"}</span>
          {ext && <span style={{
    position: "absolute",
    bottom: "2px",
    right: "0px",
    fontSize: "5px",
    fontWeight: "bold",
    padding: "1px",
    borderRadius: "2px",
    lineHeight: "1",
    minWidth: "8px",
    textAlign: "center",
    ...extStyle
  }}>
              {ext}
            </span>}
        </span>
        <code>{name}</code>
        {description && <span className="description"> - {description}</span>}
      </div>
      {children}
    </div>;
};

export const FileTree = ({children}) => {
  return <div className="file-tree" style={{
    margin: "20px",
    marginLeft: "-30px"
  }}>
      {children}
    </div>;
};

`pnpm fixture` 명령어는 테스트에 사용할 **일관된 데이터 세트**를 관리합니다. 개발 환경의 실제 데이터를 테스트 환경으로 복사하여, 안정적이고 반복 가능한 테스트를 수행할 수 있습니다.

## 기본 개념

Fixture는 테스트용 **고정된 데이터 세트**입니다:

* **일관성**: 모든 테스트가 같은 초기 데이터로 시작
* **격리**: 테스트 DB와 개발 DB 분리
* **재현성**: 같은 조건에서 반복 테스트 가능
* **관계 보존**: 외래키 관계가 유지됨

## 명령어

### init - 테스트 DB 초기화

개발 DB의 스키마를 테스트 DB로 복사합니다.

```bash theme={null}
pnpm fixture init
```

**실행 과정**:

```
DUMP...
  ✓ Schema dumped from development_master

SYNC to (REMOTE) Fixture DB...
  ✓ Database myapp_fixture created
  ✓ Schema applied

SYNC to (LOCAL) Testing DB...
  ✓ Database myapp_test created
  ✓ Schema applied

Fixture initialization completed!
```

**생성되는 데이터베이스**:

* **Fixture DB** (원격): 공유 fixture 저장소
* **Test DB** (로컬): 로컬 테스트용 DB

<Info>`init`은 **스키마만** 복사합니다. 데이터는 포함되지 않습니다.</Info>

### import - 데이터 가져오기

개발 DB에서 특정 레코드를 fixture로 저장합니다.

```bash theme={null}
pnpm fixture import
```

대화형 프롬프트가 표시됩니다:

```
? Please select entity: (Use arrow keys)
❯ User
  Post
  Comment

? Enter record IDs (comma-separated): 1,2,3

Importing fixtures...
  ✓ Imported User #1
  ✓ Imported User #2
  ✓ Imported User #3
  ✓ Imported related records (5 dependencies)

Fixture import completed!
```

**자동으로 포함되는 관련 데이터**:

* 외래키로 참조되는 레코드
* 관계 테이블의 레코드
* 역참조 레코드 (선택 가능)

### sync - 동기화

저장된 fixture를 테스트 DB에 적용합니다.

```bash theme={null}
pnpm fixture sync
```

**실행 과정**:

```
Syncing fixtures...

Clearing test database...
  ✓ Cleared all tables

Applying fixtures...
  ✓ Applied 3 users
  ✓ Applied 7 posts
  ✓ Applied 12 comments
  ✓ Applied 5 categories

Fixture sync completed!
```

<Tip>테스트 실행 전에 `sync`를 호출하면 항상 깨끗한 상태에서 시작할 수 있습니다.</Tip>

### gen - 자동 Fixture 생성

Entity의 cone 메타데이터를 활용하여 **자동으로 Fixture를 생성**합니다. 실제 데이터가 없거나 가상의 테스트 데이터가 필요할 때 유용합니다.

```bash theme={null}
pnpm fixture gen
```

**대화형 모드**:

```
? Fixture를 생성할 Entity를 선택하세요: (Use arrow keys)
❯ ◯ User
  ◯ Post
  ◯ Comment

? 각 Entity별 생성 개수: 5

? User 엔티티가 포함되어 있습니다. 생성 방식을 선택하세요:
❯ 1. 로그인 가능한 사용자 fixture 생성
  2. 확인용 데이터(로그인 불가) 생성

? 저장 방식: (Use arrow keys)
❯ Fixture DB에 저장
  파일로 저장 (자동 파일명)
  파일로 저장 (파일명 지정)
  저장 안 함 (출력만)

? LLM으로 더 현실적인 데이터를 생성할까요? (fixtureHint 기반, ANTHROPIC_API_KEY 필요) No
```

<Info>
  User 엔티티가 선택에 포함된 경우에만 생성 방식 선택 프롬프트가 표시됩니다.
  User 엔티티가 없으면 바로 저장 방식 선택으로 넘어갑니다.
</Info>

**로그인 가능한 User Fixture 생성**:

User 엔티티를 선택하고 "로그인 가능한 사용자 fixture 생성"을 선택하면, better-auth의 `sign-up/email` 엔드포인트를 통해 실제로 로그인 가능한 사용자를 생성합니다.

이 모드의 동작:

1. `FixtureGenerator`로 User 데이터(name, email, username 등)를 생성
2. `Sonamu.auth.handler()`를 통해 better-auth sign-up API를 호출하여 사용자 등록
3. 생성된 모든 사용자의 `email_verified`를 `true`로 자동 업데이트
4. 생성된 계정 목록(email, password)을 콘솔에 출력

```
로그인 가능한 사용자 5명 생성 중...
  ✅ alice@example.com 생성 완료
  ✅ bob@example.com 생성 완료
  ⚠️  carol@example.com 이미 존재 - 건너뜁니다.
  ✅ dave@example.com 생성 완료
  ✅ eve@example.com 생성 완료

email_verified = true 업데이트 완료

✅ 4명의 로그인 가능한 사용자 생성 완료

생성된 계정 목록:
┌─────────┬──────────────────────┬────────────┐
│ (index) │ email                │ password   │
├─────────┼──────────────────────┼────────────┤
│ 0       │ alice@example.com    │ Test1234!  │
│ 1       │ bob@example.com      │ Test1234!  │
│ 2       │ dave@example.com     │ Test1234!  │
│ 3       │ eve@example.com      │ Test1234!  │
└─────────┴──────────────────────┴────────────┘
```

<Warning>
  생성된 모든 사용자의 비밀번호는 `Test1234!`로 동일합니다. 이 기능은 개발/테스트 환경 전용이며,
  프로덕션 데이터베이스에서 사용해서는 안 됩니다.
</Warning>

"확인용 데이터(로그인 불가) 생성"을 선택하면 기존과 동일하게 `generateBatch()`로 DB에 직접 데이터를 삽입합니다. 이 경우 better-auth 인증 레코드가 생성되지 않으므로 로그인은 불가능합니다.

**CLI 옵션**:

| 옵션           | 설명                                                         | 예시                                           |
| ------------ | ---------------------------------------------------------- | -------------------------------------------- |
| `--all`      | 모든 Entity에 대해 생성                                           | `pnpm fixture gen --all`                     |
| `--include`  | 특정 Entity만 포함                                              | `pnpm fixture gen --include User,Post`       |
| `--exclude`  | 특정 Entity 제외 (--all과 함께 사용)                                | `pnpm fixture gen --all --exclude Comment`   |
| `--count`    | 생성할 레코드 수                                                  | `pnpm fixture gen --include User --count 10` |
| `--save-to`  | 저장 방식 지정                                                   | `pnpm fixture gen --save-to db`              |
| `--use-llm`  | LLM으로 현실적인 데이터 생성 (fixtureHint 기반, `ANTHROPIC_API_KEY` 필요) | `pnpm fixture gen --use-llm`                 |
| `--no-cache` | LLM 캐시 비활성화 (`--use-llm`과 함께 사용)                           | `pnpm fixture gen --use-llm --no-cache`      |

**저장 방식**:

* `db`: Fixture DB에 직접 저장 (기본값)
* `file`: `test/fixtures/{entity_table}.json` 파일로 저장
* `file:{filename}`: 지정된 파일명으로 저장
* `none`: 저장하지 않고 콘솔에 출력만

```bash theme={null}
# 예시: User, Post 각 10개씩 생성하여 DB에 저장
pnpm fixture gen --include User,Post --count 10 --save-to db
```

**값 생성 우선순위**:

각 필드의 값을 생성할 때 아래 순서대로 적용됩니다. 먼저 매칭되는 전략이 사용되고, 이후 단계는 건너뜁니다.

1. **Relation** — 외래키 관계가 있는 필드는 자동으로 관련 레코드를 생성/참조합니다.
2. **cone.note + LLM** — `--use-llm` 옵션이 켜져 있고, 해당 필드의 cone에 `note`가 지정된 경우 LLM이 `note` 내용을 기반으로 현실적인 값을 생성합니다. LLM 호출이 실패하면 다음 단계로 폴백합니다.
3. **cone.fixtureGenerator** — cone에 `fixtureGenerator`(Faker 표현식 등)가 지정된 경우 이를 실행합니다.
4. **cone.fixtureDefault** — cone에 `fixtureDefault` 고정값이 지정된 경우 이를 사용합니다.
5. **타입별 기본 생성** — 위 어떤 전략도 해당하지 않으면 필드 타입(string, number, date 등)에 따라 자동 생성합니다.

<Info>
  `--use-llm` 사용 시 cone.note가 있는 필드는 `fixtureGenerator`보다 LLM이 우선합니다. 이를 통해
  도메인에 맞는 더 현실적인 데이터를 생성할 수 있습니다.
</Info>

<Info>
  LLM이 생성한 문자열이 해당 필드의 `length` 제약을 초과하면 자동으로 잘라냅니다. 예를 들어
  `varchar(100)` 필드에 대해 LLM이 120자를 생성하면 앞 100자만 사용됩니다.
</Info>

**Companion Fixture 자동 생성**:

Entity의 cone에 `fixtureCompanions`가 선언되어 있으면, 부모 fixture 생성 시 companion Entity의 fixture도 함께 자동 생성됩니다. 예를 들어 better-auth 기반 프로젝트에서 User fixture를 생성하면 credentials Account도 자동으로 생성됩니다.

`fixtureCompanions`는 Entity의 `id` prop cone에 선언합니다:

```json theme={null}
{
  "name": "id",
  "type": "string",
  "cone": {
    "fixtureStrategy": "sequence",
    "fixtureCompanions": [
      {
        "entity": "Account",
        "overrides": {
          "provider_id": "credential",
          "account_id": "{{email}}",
          "password": "{{email}}"
        }
      }
    ]
  }
}
```

| 속성          | 설명                                                                                   |
| ----------- | ------------------------------------------------------------------------------------ |
| `entity`    | 함께 생성할 companion Entity 이름                                                           |
| `overrides` | companion fixture에 적용할 고정 오버라이드 값. `{{fieldName}}` 형식으로 부모 fixture의 필드 값을 참조할 수 있습니다 |
| `count`     | 부모 1개당 생성할 companion 개수 (기본값: 1)                                                     |

better-auth를 사용하는 기존 프로젝트에 `fixtureCompanions`를 소급 적용하려면:

```bash theme={null}
pnpm sonamu auth add-companions
```

이 명령어는 better-auth Entity(User 등)의 `entity.json`에 `fixtureCompanions` 설정이 없는 경우에만 자동으로 추가합니다. 이미 설정이 있는 Entity는 건너뜁니다.

**parentId 엔티티 Fixture 생성**:

`parentId`가 지정된 엔티티(상속 관계의 서브타입)는 `gen` 시 특별한 방식으로 처리됩니다. 새로운 부모 레코드를 생성하는 대신, DB에서 아직 서브타입 행이 없는 기존 부모 레코드를 찾아 해당 ID를 사용합니다.

예를 들어 `Paper`가 `Achievement`의 서브타입(`parentId: "Achievement"`)인 경우:

1. `achievements` 테이블에서 아직 `papers` 테이블에 대응하는 행이 없는 ID를 조회
2. 해당 ID로 `Paper` fixture를 생성하여 부모-자식 관계를 형성

부모 조회 시 조건을 추가하려면 `id` prop의 cone에 `fixtureParentOverrides`를 지정합니다:

```json theme={null}
{
  "name": "id",
  "type": "integer",
  "cone": {
    "fixtureParentOverrides": {
      "achievement_type": "PAPER"
    }
  }
}
```

| 속성                       | 설명                                                                               |
| ------------------------ | -------------------------------------------------------------------------------- |
| `fixtureParentOverrides` | 부모 테이블에서 사용 가능한 레코드를 조회할 때 적용할 WHERE 조건. 예를 들어 서브타입별로 구분 컬럼이 있는 경우 해당 값으로 필터링합니다 |

<Warning>
  부모 테이블에 서브타입이 없는 레코드가 부족하면 해당 엔티티의 fixture 생성을 건너뜁니다. 사전에
  부모 엔티티의 fixture를 충분히 생성해 두어야 합니다.
</Warning>

### fetch - 운영 DB에서 데이터 가져오기

**실제 운영 DB(또는 개발 DB)에서 데이터를 가져와** Fixture DB에 저장합니다. 현실적인 테스트 데이터가 필요할 때 사용합니다.

```bash theme={null}
pnpm fixture fetch
```

**대화형 모드**:

```
? Import할 Entity를 선택하세요: (Use arrow keys)
❯ ◯ User
  ◯ Post
  ◯ Comment

User, Post import 중...
  ✓ User: 10개 import 완료
  ✓ Post: 10개 import 완료
```

**CLI 옵션**:

| 옵션           | 설명               | 예시                                       |
| ------------ | ---------------- | ---------------------------------------- |
| `--all`      | 모든 Entity에서 가져오기 | `pnpm fixture fetch --all`               |
| `--include`  | 특정 Entity만 포함    | `pnpm fixture fetch --include User,Post` |
| `--exclude`  | 특정 Entity 제외     | `pnpm fixture fetch --all --exclude Log` |
| `--strategy` | 데이터 선택 전략        | `pnpm fixture fetch --strategy recent`   |
| `--limit`    | 가져올 레코드 수        | `pnpm fixture fetch --limit 20`          |

**데이터 선택 전략**:

* `recent` (기본값): 최근 데이터부터 가져옴
* `sample`: 무작위 샘플링

```bash theme={null}
# 예시: User의 최근 20개 데이터 가져오기
pnpm fixture fetch --include User --strategy recent --limit 20
```

**특징**:

* 관계된 데이터도 함께 자동으로 가져옴 (깊이 2 레벨까지)
* 외래키 참조 무결성 유지

### explore - DB 데이터 조회

DB의 데이터를 **조회만** 합니다. 저장하지 않고 빠르게 데이터를 확인할 때 유용합니다.

```bash theme={null}
pnpm fixture explore
```

**대화형 모드**:

```
? 탐색할 Entity: User

User 10개 조회 완료:
┌─────────┬────┬────────────────────┬──────────┐
│ (index) │ id │ email              │ name     │
├─────────┼────┼────────────────────┼──────────┤
│    0    │  1 │ 'user1@example.com'│ '홍길동' │
│    1    │  2 │ 'user2@example.com'│ '김철수' │
│   ...   │... │ ...                │ ...      │
└─────────┴────┴────────────────────┴──────────┘
```

**CLI 옵션**:

| 옵션           | 설명         | 예시                                       |
| ------------ | ---------- | ---------------------------------------- |
| `--include`  | 탐색할 Entity | `pnpm fixture explore --include User`    |
| `--strategy` | 데이터 선택 전략  | `pnpm fixture explore --strategy sample` |
| `--limit`    | 조회할 레코드 수  | `pnpm fixture explore --limit 5`         |

**데이터 선택 전략**:

* `sample` (기본값): 무작위 샘플
* `recent`: 최근 데이터
* `random`: 완전 무작위
* `ids`: 특정 ID 지정
* `query`: 커스텀 쿼리

```bash theme={null}
# 예시: User 테이블에서 최근 5개 조회
pnpm fixture explore --include User --strategy recent --limit 5
```

## 사용 워크플로우

### 1. 초기 설정

프로젝트 시작 시 한 번만 실행합니다.

```bash theme={null}
# 테스트 DB 생성 및 스키마 복사
pnpm fixture init
```

### 2. 필요한 데이터 선택

테스트에 필요한 실제 데이터를 가져옵니다.

```bash theme={null}
# User #1, #2, #3 가져오기
pnpm fixture import

# Entity: User
# IDs: 1,2,3
```

### 3. 테스트 작성

```typescript title="user.model.test.ts" theme={null}
import { FixtureManager } from "sonamu/test";

beforeAll(async () => {
  // Fixture 동기화
  await FixtureManager.sync();
});

test("User를 이메일로 찾기", async () => {
  // Fixture의 User #1 사용
  const user = await UserModel.findByEmail("user1@example.com");

  expect(user).toBeDefined();
  expect(user.id).toBe(1);
});
```

### 4. 테스트 실행

```bash theme={null}
pnpm test
```

테스트가 실행될 때마다 fixture가 자동으로 동기화됩니다.

## Fixture 파일

### 저장 위치

<FileTree>
  <FileItem name="src/">
    <FileItem name="fixtures/">
      <FileItem name="users.json" description="User fixture" />

      <FileItem name="posts.json" description="Post fixture" />

      <FileItem name="comments.json" description="Comment fixture" />
    </FileItem>
  </FileItem>
</FileTree>

### 파일 형식

```json title="src/fixtures/users.json" theme={null}
[
  {
    "id": 1,
    "email": "user1@example.com",
    "name": "홍길동",
    "created_at": "2024-01-15T00:00:00.000Z"
  },
  {
    "id": 2,
    "email": "user2@example.com",
    "name": "김철수",
    "created_at": "2024-01-16T00:00:00.000Z"
  }
]
```

**특징**:

* JSON 형식
* Entity별로 파일 분리
* 관계 데이터 자동 포함
* Git으로 버전 관리

## 관계 데이터 처리

### 외래키 자동 포함

Post를 import하면 연결된 User도 자동으로 포함됩니다.

```bash theme={null}
pnpm fixture import

# Entity: Post
# IDs: 1

# 자동으로 포함됨:
# ✓ Post #1
# ✓ User #5 (author)
# ✓ Category #2 (category)
```

### N:M 관계 처리

다대다 관계 테이블도 자동으로 포함됩니다.

```bash theme={null}
pnpm fixture import

# Entity: Post
# IDs: 1

# 자동으로 포함됨:
# ✓ Post #1
# ✓ post_tags (중간 테이블)
# ✓ Tag #1, #2, #3 (연결된 태그들)
```

## 테스트 격리

### 각 테스트마다 초기화

```typescript theme={null}
describe("User CRUD", () => {
  beforeEach(async () => {
    // 각 테스트 전에 fixture 재적용
    await FixtureManager.sync();
  });

  test("사용자 생성", async () => {
    const user = await UserModel.create({
      email: "new@example.com",
      name: "신규사용자",
    });

    expect(user.id).toBeDefined();
  });

  test("사용자 삭제", async () => {
    await UserModel.deleteById(1);

    const user = await UserModel.findById(1);
    expect(user).toBeNull();
  });
});
```

**격리 효과**:

* 각 테스트가 독립적
* 테스트 순서 무관
* 병렬 실행 가능

### 트랜잭션 격리

```typescript theme={null}
import { Sonamu } from "sonamu";

test("트랜잭션 테스트", async () => {
  await Sonamu.runScript(async () => {
    // 트랜잭션 내에서 실행
    const user = await UserModel.create({...});
    const post = await PostModel.create({...});

    // 테스트 완료 후 자동 롤백
  });
});
```

## 실전 예제

### 복잡한 관계 테스트

```typescript theme={null}
describe("Post with Comments", () => {
  beforeAll(async () => {
    // Post #1 (+ User #1, Comments)
    await FixtureManager.sync();
  });

  test("댓글이 있는 Post 조회", async () => {
    const post = await PostModel.findById(1, {
      include: ["comments"],
    });

    expect(post.comments).toHaveLength(3);
    expect(post.comments[0].author_id).toBe(2);
  });
});
```

### 특정 시나리오 데이터

```typescript theme={null}
describe("Premium User Features", () => {
  beforeAll(async () => {
    // Premium user만 import
    await FixtureManager.importFixture("User", [10, 11, 12]);
    await FixtureManager.sync();
  });

  test("프리미엄 기능 접근", async () => {
    const user = await UserModel.findById(10);

    expect(user.isPremium).toBe(true);
    expect(await user.canAccessFeature("advanced")).toBe(true);
  });
});
```

## 문제 해결

### Fixture DB 연결 실패

**문제**: 원격 fixture DB에 접근 불가

```
Error: connect ECONNREFUSED
```

**해결**:

```typescript title="sonamu.config.ts" theme={null}
export default {
  database: {
    fixture: {
      client: "pg",
      connection: {
        host: "fixture-db.example.com", // 올바른 호스트
        database: "myapp_fixture",
        user: "postgres",
        password: process.env.FIXTURE_DB_PASSWORD,
      },
    },
  },
};
```

### 관계 데이터 누락

**문제**: 외래키 에러 발생

```
Error: Foreign key constraint violation
```

**해결**:

```bash theme={null}
# 관련 데이터를 먼저 import
pnpm fixture import
# Entity: User
# IDs: 1,2,3

pnpm fixture import
# Entity: Post
# IDs: 1,2,3  # User 1,2,3을 참조
```

### Fixture 충돌

**문제**: ID 중복

```
Error: Duplicate entry '1' for key 'PRIMARY'
```

**해결**:

```bash theme={null}
# Test DB 초기화
pnpm fixture init

# Fixture 재동기화
pnpm fixture sync
```

## 베스트 프랙티스

### 1. 최소한의 데이터

```bash theme={null}
# ❌ 모든 데이터 import
pnpm fixture import
# IDs: 1-1000

# ✅ 필요한 데이터만
pnpm fixture import
# IDs: 1,2,3  # 대표 케이스만
```

### 2. 의미있는 데이터

```json theme={null}
// ✅ 테스트 목적이 명확한 데이터
{
  "id": 1,
  "email": "admin@example.com",
  "role": "admin",
  "name": "관리자"
}
{
  "id": 2,
  "email": "user@example.com",
  "role": "user",
  "name": "일반사용자"
}
```

### 3. 버전 관리

```bash theme={null}
# Fixture 파일을 Git에 포함
git add src/fixtures/
git commit -m "Update test fixtures"
```

### 4. CI/CD 통합

```yaml title=".github/workflows/test.yml" theme={null}
jobs:
  test:
    steps:
      - name: Setup Database
        run: |
          pnpm fixture init
          pnpm fixture sync

      - name: Run Tests
        run: pnpm test
```

## 다음 단계

<CardGroup cols={2}>
  <Card title="Testing" icon="flask" href="/ko/testing/naite">
    Naite 테스트 프레임워크 알아보기
  </Card>

  <Card title="migrate" icon="database" href="/ko/tools-and-cli/sonamu-cli/migrate">
    마이그레이션 관리하기
  </Card>
</CardGroup>
