一键导入
sea-test-generator
Generate comprehensive tests from SEA specifications. Creates unit tests, integration tests, and E2E tests with proper coverage.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Generate comprehensive tests from SEA specifications. Creates unit tests, integration tests, and E2E tests with proper coverage.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends AI agent capabilities with specialized knowledge, workflows, or tool integrations.
Use when creating, scaffolding, or updating the repository AI harness in VS Code. Trigger on requests to bootstrap or refresh instructions, prompts, agents, hooks, skills, or memory layers while preserving useful existing structure and avoiding monolithic always-on context.
Canonical SEA-Forge release workflow for release preparation, validation, branch promotion, tagging, rollback, and release-gate evidence. Use when preparing a release, promoting dev to stage or stage to main, cutting a version tag, validating release readiness, handling hotfix forward-merges, planning rollback, or executing a full end-to-end release. Prefer existing just recipes and evidence-producing gates over ad hoc deployment steps.
Use when SEA work touches specs, generators, manifests, semantic fixtures, regeneration, `src/gen`, or behavior projected from ADR/PRD/SDS/SEA authority.
Use when a SEA context has valid specs and generated contracts but lacks handwritten logic, runtime wiring, persistence, integration, tests, or executable proof.
Create concise, copy-paste-ready bridge prompts between a repo-aware coding agent and an external strategic reasoning agent such as S1 ArchDevAgent. Use when the user wants to offload architecture, research, large refactor planning, migration design, plan critique, implementation-spec drafting, test-strategy design, whole-repo analysis, or diff review to an external agent while keeping repo inspection and implementation in the coding agent. Also use when the user says "use S1", "ask S1", "make a prompt for S1", "bridge this to my external agent", "prepare a context packet", or "validate this S1 response".
| name | sea-test-generator |
| description | Generate comprehensive tests from SEA specifications. Creates unit tests, integration tests, and E2E tests with proper coverage. |
Generate comprehensive test suites from SEA specifications including unit tests, integration tests, and E2E tests with proper mocking, fixtures, and coverage.
Invoke when:
User invokes with:
Claude invokes automatically:
# Extract test scenarios from spec
SCENARIOS=$(yq e '.test_scenarios // []' "$SPEC_FILE")
# Extract endpoints
ENDPOINTS=$(yq e '.api_surface.endpoints[]' "$SPEC_FILE")
# Extract schemas
SCHEMAS=$(yq e '.api_surface.schemas[]' "$SPEC_FILE")
# Extract business rules
RULES=$(yq e '.domain.rules // []' "$SPEC_FILE")
// tests/unit/handlers/create-user.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { CreateUserHandler } from '../../src/gen/handlers/create-user';
import type { Context } from '@sprime01/sea';
describe('CreateUserHandler', () => {
let handler: CreateUserHandler;
let mockContext: Context;
let mockDomain: any;
beforeEach(() => {
mockDomain = {
create: vi.fn(),
findById: vi.fn(),
update: vi.fn(),
delete: vi.fn()
};
mockContext = {
domain: mockDomain,
logger: {
info: vi.fn(),
error: vi.fn(),
warn: vi.fn()
},
events: {
publish: vi.fn()
}
} as unknown as Context;
handler = new CreateUserHandler(mockContext, mockContext.logger);
});
describe('handle', () => {
const validRequest = {
body: {
name: 'John Doe',
email: 'john@example.com',
preferences: { theme: 'dark' }
}
};
it('should create user with valid request', async () => {
const expectedUser = {
id: '123',
name: 'John Doe',
email: 'john@example.com',
preferences: { theme: 'dark' },
createdAt: new Date()
};
mockDomain.create.mockResolvedValue(expectedUser);
const response = await handler.handle(validRequest);
expect(response.status).toBe(200);
expect(response.body).toEqual(expectedUser);
expect(mockDomain.create).toHaveBeenCalledWith(validRequest.body);
expect(mockContext.events.publish).toHaveBeenCalledWith({
type: 'UserCreated',
payload: expectedUser
});
});
it('should reject invalid email', async () => {
const invalidRequest = {
body: {
name: 'John Doe',
email: 'not-an-email',
preferences: {}
}
};
const response = await handler.handle(invalidRequest);
expect(response.status).toBe(400);
expect(response.body.error).toBe('Validation error');
expect(mockDomain.create).not.toHaveBeenCalled();
});
it('should reject missing name', async () => {
const invalidRequest = {
body: {
email: 'john@example.com'
}
};
const response = await handler.handle(invalidRequest);
expect(response.status).toBe(400);
expect(response.body.error).toBe('Validation error');
});
it('should handle domain errors', async () => {
mockDomain.create.mockRejectedValue(new Error('Duplicate email'));
const response = await handler.handle(validRequest);
expect(response.status).toBe(500);
expect(response.body.error).toBe('Internal server error');
expect(mockContext.logger.error).toHaveBeenCalledWith(
'Handler error',
expect.any(Object)
);
});
it('should sanitize preferences', async () => {
const requestWithScript = {
body: {
name: 'John Doe',
email: 'john@example.com',
preferences: {
theme: 'dark',
script: '<script>alert("xss")</script>'
}
}
};
const sanitizedUser = {
id: '123',
name: 'John Doe',
email: 'john@example.com',
preferences: { theme: 'dark' },
createdAt: new Date()
};
mockDomain.create.mockResolvedValue(sanitizedUser);
const response = await handler.handle(requestWithScript);
expect(response.status).toBe(200);
expect(response.body.preferences).not.toHaveProperty('script');
});
});
});
// tests/integration/user-api.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { PostgreSqlContainer } from '@testcontainers/postgresql';
import { setupTestApp } from '../helpers/test-setup';
import type { TestContext } from '../helpers/types';
describe('User API Integration Tests', () => {
let container: PostgreSqlContainer;
let context: TestContext;
beforeAll(async () => {
// Start test database
container = await PostgreSqlContainer.start();
const connectionString = container.getConnectionUri();
// Setup test application
context = await setupTestApp({
database: { connectionString }
});
// Run migrations
await context.migrate.up();
});
afterAll(async () => {
await context.app.close();
await container.stop();
});
describe('POST /api/users', () => {
it('should create user and persist to database', async () => {
const response = await context.request
.post('/api/users')
.send({
name: 'Jane Doe',
email: 'jane@example.com'
});
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe('Jane Doe');
expect(response.body.email).toBe('jane@example.com');
// Verify in database
const dbUser = await context.db.query(
'SELECT * FROM users WHERE id = $1',
[response.body.id]
);
expect(dbUser.rows[0]).toMatchObject({
name: 'Jane Doe',
email: 'jane@example.com'
});
});
it('should reject duplicate email', async () => {
const user = {
name: 'John Doe',
email: 'john@example.com'
};
// First request
await context.request.post('/api/users').send(user);
// Duplicate request
const response = await context.request.post('/api/users').send(user);
expect(response.status).toBe(409);
expect(response.body.error).toContain('email');
});
it('should publish UserCreated event', async () => {
const eventsPromise = context.waitForEvent('UserCreated');
await context.request.post('/api/users').send({
name: 'Event User',
email: 'event@example.com'
});
const event = await eventsPromise;
expect(event).toMatchObject({
type: 'UserCreated',
payload: {
name: 'Event User',
email: 'event@example.com'
}
});
});
});
});
// tests/e2e/user-workflow.spec.ts
import { test, expect } from '@playwright/test';
test.describe('User Management E2E', () => {
test.beforeEach(async ({ page }) => {
// Navigate to application
await page.goto('/');
// Login as admin
await page.fill('[name="email"]', 'admin@example.com');
await page.fill('[name="password"]', 'password');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
});
test('complete user creation workflow', async ({ page }) => {
// Navigate to users page
await page.click('a[href="/users"]');
await expect(page).toHaveURL('/users');
// Click create user button
await page.click('button:has-text("Create User")');
await expect(page.locator('h1')).toContainText('Create User');
// Fill form
await page.fill('[name="name"]', 'E2E Test User');
await page.fill('[name="email"]', 'e2e@example.com');
await page.selectOption('[name="role"]', 'user');
// Submit form
await page.click('button:has-text("Create")');
// Verify success message
await expect(page.locator('.alert-success')).toContainText(
'User created successfully'
);
// Verify user appears in list
await expect(page.locator('table')).toContainText('e2e@example.com');
// Click on user to view details
await page.click('text=e2e@example.com');
await expect(page).toHaveURL(/\/users\/[a-z0-9-]+/);
// Verify details
await expect(page.locator('h1')).toContainText('E2E Test User');
await expect(page.locator('[data-testid="user-email"]')).toContainText(
'e2e@example.com'
);
});
test('validation prevents invalid user creation', async ({ page }) => {
await page.goto('/users/create');
// Try to submit empty form
await page.click('button:has-text("Create")');
// Check validation errors
await expect(page.locator('[data-testid="name-error"]')).toContainText(
'Name is required'
);
await expect(page.locator('[data-testid="email-error"]')).toContainText(
'Email is required'
);
// Try invalid email
await page.fill('[name="name"]', 'Test User');
await page.fill('[name="email"]', 'not-an-email');
await page.click('button:has-text("Create")');
await expect(page.locator('[data-testid="email-error"]')).toContainText(
'Invalid email format'
);
});
test('user search and filter', async ({ page }) => {
await page.goto('/users');
// Use search box
await page.fill('[name="search"]', 'admin');
await page.press('[name="search"]', 'Enter');
// Wait for results
await expect(page.locator('table tbody tr')).toHaveCount(1);
await expect(page.locator('table')).toContainText('admin@example.com');
// Clear search
await page.fill('[name="search"]', '');
await page.press('[name="search"]', 'Enter');
// Verify more results
await expect(page.locator('table tbody tr')).toHaveCountGreaterThan(1);
});
});
// tests/fixtures/users.ts
import { faker } from '@faker-js/faker';
export interface UserFixture {
id: string;
name: string;
email: string;
preferences: Record<string, unknown>;
createdAt: Date;
}
export function createUserFixture(overrides?: Partial<UserFixture>): UserFixture {
return {
id: faker.string.uuid(),
name: faker.person.fullName(),
email: faker.internet.email(),
preferences: {
theme: faker.helpers.arrayElement(['light', 'dark', 'auto']),
notifications: faker.datatype.boolean()
},
createdAt: faker.date.past(),
...overrides
};
}
export function createUserFixtures(count: number): UserFixture[] {
return Array.from({ length: count }, () => createUserFixture());
}
// Specific fixtures for common scenarios
export const validUserFixture = () => createUserFixture({
name: 'Valid User',
email: 'valid@example.com'
});
export const invalidEmailFixture = () => createUserFixture({
email: 'not-an-email'
});
export const duplicateEmailFixture = () => createUserFixture({
email: 'duplicate@example.com'
});
// tests/helpers/test-setup.ts
import { Express } from 'express';
import request from 'supertest';
import { setupApp } from '../../src/app';
import type { Database } from '../../src/infrastructure/database';
export interface TestContext {
app: Express;
request: request.SuperTest<request.Test>;
db: Database;
migrate: {
up: () => Promise<void>;
down: () => Promise<void>;
};
waitForEvent: (eventType: string) => Promise<any>;
}
export async function setupTestApp(options?: {
database?: { connectionString: string };
}): Promise<TestContext> {
const app = await setupApp(options);
return {
app,
request: request(app),
db: app.get('database'),
migrate: {
up: async () => {
await app.get('database').migrate.up();
},
down: async () => {
await app.get('database').migrate.down();
}
},
waitForEvent: (eventType: string) => {
return new Promise((resolve) => {
const eventBus = app.get('eventBus');
eventBus.once(eventType, resolve);
});
}
};
}
| Type | Minimum Coverage |
|---|---|
| Unit Tests | 80% |
| Integration Tests | 60% |
| E2E Tests | Critical paths only |
tests/
├── unit/
│ ├── handlers/ # Handler unit tests
│ ├── domain/ # Domain logic tests
│ └── infrastructure/ # Infrastructure tests
├── integration/
│ ├── api/ # API integration tests
│ └── database/ # Database integration tests
├── e2e/
│ ├── workflows/ # E2E workflow tests
│ └── scenarios/ # User scenario tests
├── fixtures/ # Test fixtures
└── helpers/ # Test helpers
sea-handler-gen for test generation after handlersAfter test generation:
## Tests Generated
**Context**: {context}
**Spec**: spec/context.yaml
### Test Files Created
- Unit tests: {count}
- Integration tests: {count}
- E2E tests: {count}
- Fixtures: {count}
- Helpers: {count}
### Coverage Estimate
- Unit: {estimated}%
- Integration: {estimated}%
- E2E: {critical paths}
### Next Steps
1. Review generated tests
2. Run tests: pnpm test
3. Check coverage: pnpm test:coverage
4. Add missing assertions
5. Implement missing domain logic