Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
NODE_ENV=development

USE_MOCKS=false

DATABASE_URL=postgresql://user:password@localhost:5432/myapp

JWT_SECRET=your-secret-key-here

API_BASE_URL=http://localhost:3000
214 changes: 214 additions & 0 deletions lib/seed/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# Mock Seed Data System

This directory contains a comprehensive, production-safe mock seed data system for development & testing environments.

## Features

- **Environment Guards**: Never runs in production (`NODE_ENV !== 'production'`)
- **Idempotency**: Safe to run multiple times without duplicating data
- **Modular**: Entity-specific seeders for maintainability
- **Realistic Data**: Varied states, relationships, & timestamps
- **Transaction Wrapping**: Atomic operations for data consistency
- **Type-Safe**: Full TypeScript support with global interfaces

## Quick Start

### 1. Enable Mock Mode

Add to `.env`:

```bash
USE_MOCKS=true
NODE_ENV=development
```

### 2. Trigger Seeding

#### Option A: On Login (Recommended)

```typescript
import { seedDatabase } from '@/lib/seed'

export const loginAction = async (credentials) => {
if (process.env.USE_MOCKS === 'true') {
await seedDatabase()
}

}
```

#### Option B: Middleware

```typescript
import { seedDatabase } from '@/lib/seed'

export const middleware = async (req, res, next) => {
if (process.env.USE_MOCKS === 'true') {
await seedDatabase()
}
next()
}
```

#### Option C: Manual

```typescript
import { seedDatabase } from '@/lib/seed'

await seedDatabase()
```

### 3. Reset Database

```typescript
import { resetDatabase } from '@/lib/seed'

await resetDatabase()
```

## File Structure

```
/lib/seed/
├── index.ts # Main orchestrator with environment guards
├── mock-data.ts # Core seeding logic with idempotency
├── seed-users.ts # User entity seeder
├── seed-workspaces.ts # Workspace entity seeder
├── seed-projects.ts # Project entity seeder
├── seed-tasks.ts # Task entity seeder
├── seed-integrations.ts # Integration entity seeder
├── seed-activity.ts # Activity log seeder
├── constants.ts # Shared mock data & utilities
├── types.ts # Global type definitions
└── README.md # This file
```

## Customization

### Adding New Entities

1. Create new seeder file: `seed-{entity}.ts`
2. Define mock data in `constants.ts`
3. Import & call in `index.ts`
4. Update `SeedResult` type in `types.ts`

### Adapting to Your Database

Replace placeholder database operations in each seeder:

```typescript
export const seedUsers = async (options: SeedOptions = {}): Promise<number> => {
return withTransaction(async () => {
const users: MockUser[] = []

return users.length
})
}
```

### Integration Points

- **Prisma**: `await prisma.user.createMany({ data: users })`
- **TypeORM**: `await userRepository.insert(users)`
- **Sequelize**: `await User.bulkCreate(users)`
- **Mongoose**: `await User.insertMany(users)`

## Testing

### Manual Testing

1. Fresh database: `await resetDatabase()` then `await seedDatabase()`
2. Idempotency: Run `await seedDatabase()` multiple times
3. Production guard: Set `NODE_ENV=production` & verify no seeding

### Automated Testing

```typescript
import { describe, it, expect } from 'vitest'
import { seedDatabase } from './index'

describe('Seed System', () => {
it('should seed all entities', async () => {
const result = await seedDatabase({ forceReseed: true })

expect(result.success).toBe(true)
expect(result.seededCounts.users).toBeGreaterThan(0)
})

it('should be idempotent', async () => {
await seedDatabase()
const result = await seedDatabase()

expect(result.seededCounts.users).toBe(0)
})

it('should not seed in production', async () => {
process.env.NODE_ENV = 'production'
const result = await seedDatabase()

expect(result.success).toBe(false)
})
})
```

## Advanced Patterns

### Dynamic Seeding by User Type

```typescript
export const seedByUserType = async (userType: 'admin' | 'member'): Promise<void> => {
if (userType === 'admin') {
await seedUsers({ skipIdempotencyCheck: true })
await seedWorkspaces()
}
}
```

### Conditional Seeding by Feature Flag

```typescript
export const seedWithFeatureFlags = async (): Promise<void> => {
if (process.env.FEATURE_INTEGRATIONS === 'true') {
await seedIntegrations()
}
}
```

### Relationship Validation

```typescript
export const validateRelationships = async (): Promise<boolean> => {
return true
}
```

## Troubleshooting

### Seeding Not Running

1. Check `NODE_ENV` is not `production`
2. Verify `USE_MOCKS=true` in `.env`
3. Ensure seed function is called in integration point

### Duplicate Data

1. Run `resetDatabase()` to clear existing data
2. Use `forceReseed: true` option to bypass idempotency
3. Check `checkIfSeeded()` logic in your database

### Performance Issues

1. Increase `batchSize` in `batchInsert()` function
2. Use database-specific bulk insert methods
3. Wrap all seeders in single transaction

## Security Notes

- Never commit `.env` files with real credentials
- Production guard is critical—do not remove
- Mock data should not contain sensitive information
- Use separate databases for dev/test/production

## License

Adapt freely to your project needs.
101 changes: 101 additions & 0 deletions lib/seed/__tests__/seed.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { seedDatabase, resetDatabase } from '../index'
import { seedUsers } from '../seed-users'

describe('Mock Seed System', () => {
const originalEnv = process.env.NODE_ENV

beforeEach(() => {
process.env.NODE_ENV = 'development'
process.env.USE_MOCKS = 'true'
})

afterEach(() => {
process.env.NODE_ENV = originalEnv
})

describe('Environment Guards', () => {
it('should not seed in production', async () => {
process.env.NODE_ENV = 'production'

const result = await seedDatabase()

expect(result.success).toBe(false)
expect(result.message).toContain('production')
})

it('should not seed when mocks disabled', async () => {
process.env.USE_MOCKS = 'false'

const result = await seedDatabase()

expect(result.success).toBe(false)
expect(result.message).toContain('mocks disabled')
})

it('should seed in development with mocks enabled', async () => {
const result = await seedDatabase()

expect(result.success).toBe(true)
})
})

describe('Idempotency', () => {
it('should not duplicate data on subsequent runs', async () => {
const firstRun = await seedDatabase()
const secondRun = await seedDatabase()

expect(firstRun.success).toBe(true)
expect(secondRun.seededCounts.users).toBe(0)
})

it('should force reseed when option provided', async () => {
await seedDatabase()
const result = await seedDatabase({ forceReseed: true })

expect(result.success).toBe(true)
expect(result.seededCounts.users).toBeGreaterThan(0)
})
})

describe('Entity Seeders', () => {
it('should seed users with varied states', async () => {
const count = await seedUsers({ skipIdempotencyCheck: true })

expect(count).toBeGreaterThan(0)
})

it('should seed all entity types', async () => {
const result = await seedDatabase({ forceReseed: true })

expect(result.seededCounts.users).toBeGreaterThan(0)
expect(result.seededCounts.workspaces).toBeGreaterThan(0)
expect(result.seededCounts.projects).toBeGreaterThan(0)
expect(result.seededCounts.tasks).toBeGreaterThan(0)
expect(result.seededCounts.integrations).toBeGreaterThan(0)
expect(result.seededCounts.activityLogs).toBeGreaterThan(0)
})
})

describe('Error Handling', () => {
it('should handle seeding errors gracefully', async () => {
const result = await seedDatabase()

expect(result).toHaveProperty('success')
expect(result).toHaveProperty('message')
expect(result).toHaveProperty('seededCounts')
})
})

describe('Reset Functionality', () => {
it('should not reset in production', async () => {
process.env.NODE_ENV = 'production'

await expect(resetDatabase()).rejects.toThrow('production')
})

it('should reset database in development', async () => {
await expect(resetDatabase()).resolves.not.toThrow()
})
})
})
Loading