lobehub/.agents/skills/testing/references/db-model-test.md
Innei 346fc4617e
♻️ refactor: migrate AI Rules to Claude Code Skills (#11737)
♻️ refactor: migrate AI Rules to Claude Code Skills system

Migrate all AI Rules from .cursor/rules/ to .agents/skills/ directory:
- Move 23 skills to .agents/skills/ (main directory)
- Update symlinks: .claude/skills, .cursor/skills, .codex/skills
- Create project-overview skill from project documentation
- Add references/ subdirectories for complex skills
- Remove LobeChat references from skill descriptions
- Delete obsolete .cursor/rules/ and .claude/commands/prompts/ directories

Skills structure enables better portability and maintainability across AI tools.
2026-01-23 22:30:18 +08:00

3.2 KiB

Database Model Testing Guide

Test packages/database Model layer.

Dual Environment Verification (Required)

# 1. Client environment (fast)
cd packages/database && TEST_SERVER_DB=0 bunx vitest run --silent='passed-only' '[file]'

# 2. Server environment (compatibility)
cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' '[file]'

User Permission Check - Security First 🔒

Critical security requirement: All user data operations must include permission checks.

// ❌ DANGEROUS: Missing permission check
update = async (id: string, data: Partial<MyModel>) => {
  return this.db.update(myTable).set(data)
    .where(eq(myTable.id, id))  // Only checks ID
    .returning();
};

// ✅ SECURE: Permission check included
update = async (id: string, data: Partial<MyModel>) => {
  return this.db.update(myTable).set(data)
    .where(and(
      eq(myTable.id, id),
      eq(myTable.userId, this.userId)  // ✅ Permission check
    ))
    .returning();
};

Test File Structure

// @vitest-environment node
describe('MyModel', () => {
  describe('create', () => { /* ... */ });
  describe('queryAll', () => { /* ... */ });
  describe('update', () => {
    it('should update own records');
    it('should NOT update other users records');  // 🔒 Security
  });
  describe('delete', () => {
    it('should delete own records');
    it('should NOT delete other users records');  // 🔒 Security
  });
  describe('user isolation', () => {
    it('should enforce user data isolation');  // 🔒 Core security
  });
});

Security Test Example

it('should not update records of other users', async () => {
  const [otherUserRecord] = await serverDB
    .insert(myTable)
    .values({ userId: 'other-user', data: 'original' })
    .returning();

  const result = await myModel.update(otherUserRecord.id, { data: 'hacked' });

  expect(result).toBeUndefined();
  const unchanged = await serverDB.query.myTable.findFirst({
    where: eq(myTable.id, otherUserRecord.id),
  });
  expect(unchanged?.data).toBe('original');
});

Data Management

const userId = 'test-user';
const otherUserId = 'other-user';

beforeEach(async () => {
  await serverDB.delete(users);
  await serverDB.insert(users).values([{ id: userId }, { id: otherUserId }]);
});

afterEach(async () => {
  await serverDB.delete(users);
});

Foreign Key Handling

// ❌ Wrong: Invalid foreign key
const testData = { asyncTaskId: 'invalid-uuid', fileId: 'non-existent' };

// ✅ Correct: Use null
const testData = { asyncTaskId: null, fileId: null };

// ✅ Or: Create referenced record first
beforeEach(async () => {
  const [asyncTask] = await serverDB.insert(asyncTasks)
    .values({ id: 'valid-id', status: 'pending' }).returning();
  testData.asyncTaskId = asyncTask.id;
});

Predictable Sorting

// ✅ Use explicit timestamps
const oldDate = new Date('2024-01-01T10:00:00Z');
const newDate = new Date('2024-01-02T10:00:00Z');
await serverDB.insert(table).values([
  { ...data1, createdAt: oldDate },
  { ...data2, createdAt: newDate },
]);

// ❌ Don't rely on insert order
await serverDB.insert(table).values([data1, data2]);  // Unpredictable